Skip to content

Commit f1b66a2

Browse files
authored
Merge pull request #316 from cipherstash/refactor-logging-targets
refactor: centralize log target definitions using declarative macro
2 parents 8972c41 + a5ee595 commit f1b66a2

File tree

5 files changed

+186
-134
lines changed

5 files changed

+186
-134
lines changed

DEVELOPMENT.md

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -244,17 +244,23 @@ Default level is `Info`.
244244

245245
Proxy has multiple "log targets" corresponding to the internal domains.
246246

247-
Set log levels for a specific log target to turn on or turn of more verbose logging:
247+
Set log levels for a specific log target to turn on or turn off more verbose logging:
248248

249249
```
250250
Target | ENV
251251
--------------- | -------------------------------------
252252
DEVELOPMENT | CS_LOG__DEVELOPMENT_LEVEL
253253
AUTHENTICATION | CS_LOG__AUTHENTICATION_LEVEL
254+
CONFIG | CS_LOG__CONFIG_LEVEL
254255
CONTEXT | CS_LOG__CONTEXT_LEVEL
256+
ENCODING | CS_LOG__ENCODING_LEVEL
255257
ENCRYPT | CS_LOG__ENCRYPT_LEVEL
258+
DECRYPT | CS_LOG__DECRYPT_LEVEL
259+
ENCRYPT_CONFIG | CS_LOG__ENCRYPT_CONFIG_LEVEL
256260
KEYSET | CS_LOG__KEYSET_LEVEL
261+
MIGRATE | CS_LOG__MIGRATE_LEVEL
257262
PROTOCOL | CS_LOG__PROTOCOL_LEVEL
263+
PROXY | CS_LOG__PROXY_LEVEL
258264
MAPPER | CS_LOG__MAPPER_LEVEL
259265
SCHEMA | CS_LOG__SCHEMA_LEVEL
260266
```
@@ -547,12 +553,39 @@ Debug logging is very verbose, and targets allow configuration of granular log l
547553
A `target` is a string value that is added to the standard tracing macro calls (`debug!, error!, etc`).
548554
Log levels can be configured for each `target` individually.
549555

550-
A number of targets are already defined in `log.rs`.
556+
### Logging Architecture
551557

552-
The targets are aligned with the different components and contexts (`PROTOCOL, AUTHENTICATION, MAPPER, etc`)
558+
The logging system uses a declarative macro approach for managing log targets:
559+
560+
- **Single source of truth**: All log targets are defined in `packages/cipherstash-proxy/src/log/targets.rs` using the `define_log_targets!` macro
561+
- **Automatic generation**: The macro generates target constants, configuration struct fields, and accessor functions
562+
- **Type safety**: Uses a generated `LogTargetLevels` struct with serde flattening for config integration
563+
- **Self-documenting**: Clear separation between core config and target-specific levels
564+
565+
The targets are aligned with the different components and contexts (`PROTOCOL`, `AUTHENTICATION`, `MAPPER`, etc.).
553566

554567
There is a general `DEVELOPMENT` target for logs that don't quite fit into a specific category.
555568

569+
### Adding a New Log Target
570+
571+
To add a new log target, you only need to add **one line** to the macro in `packages/cipherstash-proxy/src/log/targets.rs`:
572+
573+
```rust
574+
define_log_targets!(
575+
(DEVELOPMENT, development_level),
576+
(AUTHENTICATION, authentication_level),
577+
// ... existing targets ...
578+
(NEW_TARGET, new_target_level), // <- Add this line
579+
);
580+
```
581+
582+
The constant name (e.g., `NEW_TARGET`) is automatically converted to a string value using `stringify!()`, so `NEW_TARGET` becomes `"NEW_TARGET"` for use in logging calls.
583+
584+
This automatically:
585+
- Creates the `NEW_TARGET` constant for use in logging calls
586+
- Generates the `new_target_level` field in `LogTargetLevels` struct
587+
- Creates the environment variable `CS_LOG__NEW_TARGET_LEVEL`
588+
- Adds the target to all logging functions and validation
556589

557590
### Available targets
558591

@@ -561,15 +594,20 @@ Target | ENV
561594
--------------- | -------------------------------------
562595
DEVELOPMENT | CS_LOG__DEVELOPMENT_LEVEL
563596
AUTHENTICATION | CS_LOG__AUTHENTICATION_LEVEL
597+
CONFIG | CS_LOG__CONFIG_LEVEL
564598
CONTEXT | CS_LOG__CONTEXT_LEVEL
599+
ENCODING | CS_LOG__ENCODING_LEVEL
565600
ENCRYPT | CS_LOG__ENCRYPT_LEVEL
601+
DECRYPT | CS_LOG__DECRYPT_LEVEL
602+
ENCRYPT_CONFIG | CS_LOG__ENCRYPT_CONFIG_LEVEL
566603
KEYSET | CS_LOG__KEYSET_LEVEL
604+
MIGRATE | CS_LOG__MIGRATE_LEVEL
567605
PROTOCOL | CS_LOG__PROTOCOL_LEVEL
606+
PROXY | CS_LOG__PROXY_LEVEL
568607
MAPPER | CS_LOG__MAPPER_LEVEL
569608
SCHEMA | CS_LOG__SCHEMA_LEVEL
570609
```
571610

572-
573611
### Example
574612

575613
The default log level for the proxy is `info`.
@@ -585,13 +623,23 @@ CS_LOG__MAPPER_LEVEL = "debug"
585623
Log `debug` output for the `MAPPER` target:
586624

587625
```rust
588-
debug!(
589-
target: MAPPER,
590-
client_id = self.context.client_id,
591-
identifier = ?identifier
592-
);
626+
debug!(
627+
target: MAPPER,
628+
client_id = self.context.client_id,
629+
identifier = ?identifier
630+
);
593631
```
594632

633+
### Implementation Details
634+
635+
The logging system uses these key components:
636+
637+
- **`define_log_targets!` macro** in `log/targets.rs`: Generates all logging infrastructure
638+
- **`LogTargetLevels` struct**: Auto-generated struct containing all target level fields
639+
- **`LogConfig` struct**: Main configuration with `#[serde(flatten)]` integration
640+
- **Target constants**: Exported for use in logging calls (e.g., `MAPPER`, `PROTOCOL`)
641+
- **Environment variables**: Auto-generated from target names (e.g., `CS_LOG__MAPPER_LEVEL`)
642+
595643
## Benchmarks
596644

597645
Benchmarking is integrated into CI, and can be run locally.

packages/cipherstash-proxy/src/config/log.rs

Lines changed: 9 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ use std::{fmt::Display, io::IsTerminal};
33
use clap::ValueEnum;
44
use serde::Deserialize;
55

6+
// Import the generated LogTargetLevels struct
7+
use crate::log::targets::LogTargetLevels;
8+
69
#[derive(Clone, Debug, Deserialize)]
710
pub struct LogConfig {
811
#[serde(default = "LogConfig::default_ansi_enabled")]
@@ -17,44 +20,10 @@ pub struct LogConfig {
1720
#[serde(default = "LogConfig::default_log_level")]
1821
pub level: LogLevel,
1922

20-
#[serde(default = "LogConfig::default_log_level")]
21-
pub development_level: LogLevel,
22-
23-
#[serde(default = "LogConfig::default_log_level")]
24-
pub authentication_level: LogLevel,
25-
26-
#[serde(default = "LogConfig::default_log_level")]
27-
pub config_level: LogLevel,
28-
29-
#[serde(default = "LogConfig::default_log_level")]
30-
pub context_level: LogLevel,
31-
32-
#[serde(default = "LogConfig::default_log_level")]
33-
pub encoding_level: LogLevel,
34-
35-
#[serde(default = "LogConfig::default_log_level")]
36-
pub encrypt_level: LogLevel,
37-
38-
#[serde(default = "LogConfig::default_log_level")]
39-
pub decrypt_level: LogLevel,
40-
41-
#[serde(default = "LogConfig::default_log_level")]
42-
pub encrypt_config_level: LogLevel,
43-
44-
#[serde(default = "LogConfig::default_log_level")]
45-
pub keyset_level: LogLevel,
46-
47-
#[serde(default = "LogConfig::default_log_level")]
48-
pub migrate_level: LogLevel,
49-
50-
#[serde(default = "LogConfig::default_log_level")]
51-
pub protocol_level: LogLevel,
52-
53-
#[serde(default = "LogConfig::default_log_level")]
54-
pub mapper_level: LogLevel,
55-
56-
#[serde(default = "LogConfig::default_log_level")]
57-
pub schema_level: LogLevel,
23+
// All log target levels - automatically generated and flattened from LogTargetLevels
24+
// To add a new target: just add it to the define_log_targets! macro in log/targets.rs
25+
#[serde(flatten)]
26+
pub targets: LogTargetLevels,
5827
}
5928

6029
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, ValueEnum)]
@@ -121,19 +90,8 @@ impl LogConfig {
12190
output: LogConfig::default_log_output(),
12291
ansi_enabled: LogConfig::default_ansi_enabled(),
12392
level,
124-
development_level: level,
125-
authentication_level: level,
126-
context_level: level,
127-
encoding_level: level,
128-
encrypt_level: level,
129-
encrypt_config_level: level,
130-
decrypt_level: level,
131-
keyset_level: level,
132-
migrate_level: level,
133-
protocol_level: level,
134-
mapper_level: level,
135-
schema_level: level,
136-
config_level: level,
93+
// All target levels automatically set using generated LogTargetLevels
94+
targets: LogTargetLevels::with_level(level),
13795
}
13896
}
13997

packages/cipherstash-proxy/src/log/mod.rs

Lines changed: 38 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod subscriber;
2+
pub mod targets;
23

34
use crate::config::{LogConfig, LogFormat};
45
use std::sync::Once;
@@ -12,22 +13,11 @@ use tracing_subscriber::{
1213
};
1314

1415
// Log targets used in logs like `debug!(target: DEVELOPMENT, "Flush message buffer");`
15-
// If you add one, make sure `log_targets()` and `log_level_for()` functions are updated.
16-
pub const DEVELOPMENT: &str = "development"; // one for various hidden "development mode" messages
17-
pub const AUTHENTICATION: &str = "authentication";
18-
pub const CONFIG: &str = "config";
19-
pub const CONTEXT: &str = "context";
20-
pub const ENCRYPT: &str = "encrypt";
21-
pub const PROXY: &str = "proxy";
22-
pub const DECRYPT: &str = "decrypt";
23-
pub const ENCODING: &str = "encoding";
24-
pub const ENCRYPT_CONFIG: &str = "encrypt_config";
25-
pub const KEYSET: &str = "keyset";
26-
pub const MIGRATE: &str = "migrate";
27-
pub const PARSER: &str = "parser";
28-
pub const PROTOCOL: &str = "protocol";
29-
pub const MAPPER: &str = "mapper";
30-
pub const SCHEMA: &str = "schema";
16+
// All targets are now defined in the targets module using the define_log_targets! macro.
17+
pub use targets::{
18+
AUTHENTICATION, CONFIG, CONTEXT, DECRYPT, DEVELOPMENT, ENCODING, ENCRYPT, ENCRYPT_CONFIG,
19+
KEYSET, MAPPER, MIGRATE, PROTOCOL, PROXY, SCHEMA,
20+
};
3121

3222
static INIT: Once = Once::new();
3323

@@ -59,6 +49,9 @@ mod tests {
5949
use crate::config::LogLevel;
6050

6151
use super::*;
52+
use crate::log::targets::{
53+
LogTargetLevels, AUTHENTICATION, CONTEXT, DEVELOPMENT, KEYSET, MAPPER, PROTOCOL, SCHEMA,
54+
};
6255
use crate::test_helpers::MockMakeWriter;
6356
use tracing::dispatcher::set_default;
6457
use tracing::{debug, error, info, trace, warn};
@@ -119,19 +112,22 @@ mod tests {
119112
output: LogConfig::default_log_output(),
120113
ansi_enabled: LogConfig::default_ansi_enabled(),
121114
level: LogLevel::Info,
122-
development_level: LogLevel::Info,
123-
authentication_level: LogLevel::Debug,
124-
context_level: LogLevel::Error,
125-
encoding_level: LogLevel::Error,
126-
encrypt_level: LogLevel::Error,
127-
encrypt_config_level: LogLevel::Error,
128-
decrypt_level: LogLevel::Error,
129-
keyset_level: LogLevel::Trace,
130-
migrate_level: LogLevel::Trace,
131-
protocol_level: LogLevel::Info,
132-
mapper_level: LogLevel::Info,
133-
schema_level: LogLevel::Info,
134-
config_level: LogLevel::Info,
115+
targets: LogTargetLevels {
116+
development_level: LogLevel::Info,
117+
authentication_level: LogLevel::Debug,
118+
context_level: LogLevel::Error,
119+
encoding_level: LogLevel::Error,
120+
encrypt_level: LogLevel::Error,
121+
encrypt_config_level: LogLevel::Error,
122+
decrypt_level: LogLevel::Error,
123+
keyset_level: LogLevel::Trace,
124+
migrate_level: LogLevel::Trace,
125+
protocol_level: LogLevel::Info,
126+
proxy_level: LogLevel::Info,
127+
mapper_level: LogLevel::Info,
128+
schema_level: LogLevel::Info,
129+
config_level: LogLevel::Info,
130+
},
135131
};
136132

137133
let subscriber =
@@ -142,48 +138,48 @@ mod tests {
142138
let _default = set_default(&subscriber.into());
143139

144140
// with development level 'info', info should be logged but not debug
145-
debug!(target: "development", "debug/development");
146-
info!(target: "development", "info/development");
141+
debug!(target: DEVELOPMENT, "debug/development");
142+
info!(target: DEVELOPMENT, "info/development");
147143
let log_contents = make_writer.get_string();
148144
assert!(!log_contents.contains("debug/development"));
149145
assert!(log_contents.contains("info/development"));
150146

151147
// with authentication level 'debug', debug should be logged but not trace
152-
trace!(target: "authentication", "trace/authentication");
153-
debug!(target: "authentication", "debug/authentication");
148+
trace!(target: AUTHENTICATION, "trace/authentication");
149+
debug!(target: AUTHENTICATION, "debug/authentication");
154150
let log_contents = make_writer.get_string();
155151
assert!(!log_contents.contains("trace/authentication"));
156152
assert!(log_contents.contains("debug/authentication"));
157153

158154
// with context level 'error', error should be logged but not warn
159-
warn!(target: "context", "warn/context");
160-
error!(target: "context", "error/context");
155+
warn!(target: CONTEXT, "warn/context");
156+
error!(target: CONTEXT, "error/context");
161157
let log_contents = make_writer.get_string();
162158
assert!(!log_contents.contains("warn/context"));
163159
assert!(log_contents.contains("error/context"));
164160

165161
// with keyset level 'trace', trace should be logged
166-
trace!(target: "keyset", "trace/keyset");
162+
trace!(target: KEYSET, "trace/keyset");
167163
let log_contents = make_writer.get_string();
168164
assert!(log_contents.contains("trace/keyset"));
169165

170166
// with protocol level 'info', info should be logged but not debug
171-
debug!(target: "protocol", "debug/protocol");
172-
info!(target: "protocol", "info/protocol");
167+
debug!(target: PROTOCOL, "debug/protocol");
168+
info!(target: PROTOCOL, "info/protocol");
173169
let log_contents = make_writer.get_string();
174170
assert!(!log_contents.contains("debug/protocol"));
175171
assert!(log_contents.contains("info/protocol"));
176172

177173
// with mapper level 'info', info should be logged but not debug
178-
debug!(target: "mapper", "debug/mapper");
179-
info!(target: "mapper", "info/mapper");
174+
debug!(target: MAPPER, "debug/mapper");
175+
info!(target: MAPPER, "info/mapper");
180176
let log_contents = make_writer.get_string();
181177
assert!(!log_contents.contains("debug/mapper"));
182178
assert!(log_contents.contains("info/mapper"));
183179

184180
// with schema level 'info', info should be logged but not debug
185-
debug!(target: "schema", "debug/schema");
186-
info!(target: "schema", "info/schema");
181+
debug!(target: SCHEMA, "debug/schema");
182+
info!(target: SCHEMA, "info/schema");
187183
let log_contents = make_writer.get_string();
188184
assert!(!log_contents.contains("debug/schema"));
189185
assert!(log_contents.contains("info/schema"));

packages/cipherstash-proxy/src/log/subscriber.rs

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,11 @@
11
use crate::config::{LogConfig, LogLevel, LogOutput};
2-
use crate::log::{AUTHENTICATION, CONTEXT, DEVELOPMENT, ENCRYPT, KEYSET, MAPPER, PROTOCOL, SCHEMA};
2+
use crate::log::targets::{log_level_for, log_targets};
33
use tracing_subscriber::filter::EnvFilter;
44
use tracing_subscriber::fmt::format::{DefaultFields, Format};
55
use tracing_subscriber::fmt::writer::BoxMakeWriter;
66
use tracing_subscriber::fmt::SubscriberBuilder;
77
use tracing_subscriber::FmtSubscriber;
88

9-
use super::DECRYPT;
10-
11-
fn log_targets() -> Vec<&'static str> {
12-
vec![
13-
DEVELOPMENT,
14-
AUTHENTICATION,
15-
CONTEXT,
16-
DECRYPT,
17-
ENCRYPT,
18-
KEYSET,
19-
PROTOCOL,
20-
MAPPER,
21-
SCHEMA,
22-
]
23-
}
24-
25-
fn log_level_for(config: &LogConfig, target: &str) -> LogLevel {
26-
match target {
27-
DEVELOPMENT => config.development_level,
28-
AUTHENTICATION => config.authentication_level,
29-
CONTEXT => config.context_level,
30-
DECRYPT => config.decrypt_level,
31-
ENCRYPT => config.encrypt_level,
32-
KEYSET => config.keyset_level,
33-
PROTOCOL => config.protocol_level,
34-
MAPPER => config.mapper_level,
35-
SCHEMA => config.schema_level,
36-
_ => config.level,
37-
}
38-
}
39-
409
pub fn builder(
4110
config: &LogConfig,
4211
) -> SubscriberBuilder<DefaultFields, Format, EnvFilter, BoxMakeWriter> {

0 commit comments

Comments
 (0)