Skip to content
4 changes: 1 addition & 3 deletions .markdownlint.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
{
"MD013": {
"line_length": 160
},
"MD013": false,
"MD033": false,
"MD041": false
}
2 changes: 2 additions & 0 deletions src/guidelines/checklist/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- [ ] Panic Means 'Stop the Program' ([M-PANIC-IS-STOP])
- [ ] Detected Programming Bugs are Panics, Not Errors ([M-PANIC-ON-BUG])
- [ ] All Magic Values and Behaviors are Documented ([M-DOCUMENTED-MAGIC])
- [ ] Use Structured Logging with Message Templates ([M-LOG-STRUCTURED])
- **Library / Interoperability**
- [ ] Types are Send ([M-TYPES-SEND])
- [ ] Native Escape Hatches ([M-ESCAPE-HATCHES])
Expand Down Expand Up @@ -73,6 +74,7 @@
[M-PANIC-IS-STOP]: ../universal/#M-PANIC-IS-STOP
[M-PANIC-ON-BUG]: ../universal/#M-PANIC-ON-BUG
[M-DOCUMENTED-MAGIC]: ../universal/#M-DOCUMENTED-MAGIC
[M-LOG-STRUCTURED]: ../universal/#M-LOG-STRUCTURED

<!-- Libs -->
[M-TYPES-SEND]: ../libs/interop/#M-TYPES-SEND
Expand Down
116 changes: 116 additions & 0 deletions src/guidelines/universal/M-LOG-STRUCTURED.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<!-- Copyright (c) Microsoft Corporation. Licensed under the MIT license. -->

## Use Structured Logging with Message Templates (M-LOG-STRUCTURED) { #M-LOG-STRUCTURED }

<why>To minimize the cost of logging and to improve filtering capabilities.</why>
<version>0.1</version>

Logging should use structured events with named properties and message templates following
the [message templates](https://messagetemplates.org/) specification.

> **Note:** Examples use the [`tracing`](https://docs.rs/tracing/) crate's `event!` macro,
but these principles apply to any logging API that supports structured logging (e.g., `log`,
`slog`, custom telemetry systems).

### Avoid String Formatting

String formatting allocates memory at runtime. Message templates defer formatting until viewing time.
We recommend that message template includes all named properties for easier inspection at viewing time.

```rust,ignore
// Bad: String formatting causes allocations
tracing::info!("file opened: {}", path);
tracing::info!(format!("file opened: {}", path));

// Good: Message templates with named properties
event!(
name: "file.open.success",
Level::INFO,
file.path = path.display(),
"file opened: {{file.path}}",
);
```

> **Note**: Use `{{property}}` the syntax in message templates which preserves the literal text
> while escaping Rust's format syntax. String formatting is deferred until logs are viewed.

### Name Your Events

Use hierarchical dot-notation: `<component>.<operation>.<state>`

```rust,ignore
// Bad: Unnamed events
event!(
Level::INFO,
file.path = file_path,
"file {{file.path}} processed succesfully",
);

// Good: Named events
event!(
name: "file.processing.success", // event identifier
Level::INFO,
file.path = file_path,
"file {{file.path}} processed succesfully",
);
```

Named events enable grouping and filtering across log entries.

### Follow OpenTelemetry Semantic Conventions

Use [OTel semantic conventions](https://opentelemetry.io/docs/specs/semconv/) for common attributes if needed.
This enables standardization and interoperability.

```rust,ignore
event!(
name: "file.write.success",
Level::INFO,
file.path = path.display(), // Standard OTel name
file.size = bytes_written, // Standard OTel name
file.directory = dir_path, // Standard OTel name
file.extension = extension, // Standard OTel name
file.operation = "write", // Custom name
"{{file.operation}} {{file.size}} bytes to {{file.path}} in {{file.directory}} extension={{file.extension}}",
);
```

Common conventions:

- HTTP: `http.request.method`, `http.response.status_code`, `url.scheme`, `url.path`, `server.address`
- File: `file.path`, `file.directory`, `file.name`, `file.extension`, `file.size`
- Database: `db.system.name`, `db.namespace`, `db.operation.name`, `db.query.text`
- Errors: `error.type`, `error.message`, `exception.type`, `exception.stacktrace`

### Redact Sensitive Data

Do not log plain sensitive data as this might lead to privacy and security incidents.

```rust,ignore
// Bad: Logs potentially sensitive data
event!(
name: "file.operation.started",
Level::INFO,
user.email = user.email, // Sensitive data
file.name = "license.txt",
"reading file {{file.name}} for user {{user.email}}",
);

// Good: Redact sensitive parts
event!(
name: "file.operation.started",
Level::INFO,
user.email.redacted = redact_email(user.email),
file.name = "license.txt",
"reading file {{file.name}} for user {{user.email.redacted}}",
);
```

Sensitive data include user email, file paths revealing user identity, filenames containing secrets or tokens,
file contents with PII, temporary file paths with session IDs and more. Consider using the [`data_privacy`](https://crates.io/crates/data_privacy) crate for consistent redaction.

### Further Reading

- [Message Templates Specification](https://messagetemplates.org/)
- [OpenTelemetry Semantic Conventions](https://opentelemetry.io/docs/specs/semconv/)
- [OWASP Logging Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html)
3 changes: 3 additions & 0 deletions src/guidelines/universal/M-STATIC-VERIFICATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ unnecessary_safety_doc = "warn"
unneeded_field_pattern = "warn"
unused_result_ok = "warn"

# May cause issues with structured logging otherwise.
literal_string_with_formatting_args = "allow"

# Define custom opt outs here
# ...
```
1 change: 1 addition & 0 deletions src/guidelines/universal/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@
{{#include M-PANIC-IS-STOP.md}}
{{#include M-PANIC-ON-BUG.md}}
{{#include M-DOCUMENTED-MAGIC.md}}
{{#include M-LOG-STRUCTURED.md}}