1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
#![cfg_attr(docsrs, feature(doc_cfg))]
#![deny(missing_docs, unreachable_pub)]
#![allow(clippy::needless_doctest_main)]
/*!
`tracing` Subscriber for structuring Stackdriver-compatible
[`LogEntry`](https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry)

This crate provides a [`Layer`](https://docs.rs/tracing-subscriber/0.2.4/tracing_subscriber/fmt/struct.Layer.html) for use with a `tracing` [`Registry`](https://docs.rs/tracing-subscriber/0.2.4/tracing_subscriber/struct.Registry.html) that formats `tracing` Spans and Events into properly-structured JSON for consumption by Google Operations Logging through the [`jsonPayload`](https://cloud.google.com/logging/docs/structured-logging) field. This includes the following behaviors and enhancements:

1. `rfc3339`-formatted timestamps for all Events
2. `severity` (in [`LogSeverity`](https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#LogSeverity) format) derived from `tracing` [`Level`](https://docs.rs/tracing/0.1.13/tracing/struct.Level.html)
3. `target` derived from the Event `target` [`Metadata`](https://docs.rs/tracing/0.1.13/tracing/struct.Metadata.html)
4. Span `name` and custom fields included under a `span` key
5. automatic nesting of `http_request.`-prefixed event fields
6. automatic camelCase-ing of all field keys (e.g. `http_request` -> `httpRequest`)
7. [`valuable`](https://docs.rs/valuable/latest/valuable/) support, including an `HttpRequest` helper `struct`

### Examples

#### Basic setup:

```rust
use tracing_subscriber::{layer::SubscriberExt, Registry};
use tracing_stackdriver::Stackdriver;

fn main() {
    let stackdriver = Stackdriver::default(); // writes to std::io::Stdout
    let subscriber = Registry::default().with(stackdriver);

    tracing::subscriber::set_global_default(subscriber).expect("Could not set up global logger");
}
```

#### Custom write location:

```rust
use tracing_subscriber::{layer::SubscriberExt, Registry};
use tracing_stackdriver::Stackdriver;

fn main() {
    let make_writer = || std::io::Stderr;
    let stackdriver = Stackdriver::with_writer(make_writer); // writes to std::io::Stderr
    let subscriber = Registry::default().with(stackdriver);

    tracing::subscriber::set_global_default(subscriber).expect("Could not set up global logger");
}
```

#### With `httpRequest` fields:

See all available fields [here](https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#HttpRequest).

```rust
// requires working global setup (see above examples)

use hyper::Request;

fn handle_request(request: Request) {
  let method = &request.method();
  let uri = &request.uri();

  tracing::info!(
    http_request.request_method = %method,
    http_request.request_url = %uri,
    "Request received"
  );

  // jsonPayload formatted as:
  // {
  //   "time": "some-timestamp"
  //   "severity": "INFO",
  //   "httpRequest": {
  //     "requestMethod": "GET",
  //     "requestUrl": "/some/url/from/request"
  //    },
  //   "message": "Request received"
  // }
}
```

### With more specific `LogSeverity` levels:

Google supports a slightly different set of severity levels than `tracing`. `tracing` levels are automatically mapped to `LogSeverity` levels, but you can customize the level beyond the intersection of `tracing` levels and `LogSeverity` levels by using the provided `LogSeverity` level with a `severity` key.

```rust
use tracing_stackdriver::LogSeverity;

fn main() {
    // requires working global setup (see above examples)

    tracing::info!(severity = %LogSeverity::Notice, "Application starting");

  // jsonPayload formatted as:
  // {
  //   "time": "some-timestamp"
  //   "severity": "NOTICE",
  //   "message": "Request received"
  // }
}
```

#### With `valuable` support:

`tracing_stackdriver` supports deeply-nested structured logging through `tracing`'s [unstable `valuable` support](https://github.com/tokio-rs/tracing/discussions/1906). In addition, `httpRequest` fields can be generated with the `HttpRequest` helper struct exported from this library for better compile-time checking of fields.

To enable `valuable` support, use the `valuable` feature flag and compile your project with `RUSTFLAGS="--cfg tracing_unstable"`.

```rust

// requires working global setup (see above examples)

use hyper::Request;
use tracing_stackdriver::HttpRequest;
use valuable::Valuable;

#[derive(Valuable)]
struct StructuredLog {
    service: &'static str,
    handler: &'static str
}


fn handle_request(request: Request) {
  let http_request = HttpRequest {
      request_method: request.method().into(),
      request_url: request.uri().into(),
      ..Default::default()
  };

  let structured_log = StructuredLog {
      service: "request_handlers",
      handler: "handle_request",
  };

  tracing::info!(
    http_request = http_request.as_value(),
    structured_log = structured_log.as_value(),
    "Request received"
  );

  // jsonPayload formatted as:
  // {
  //   "time": "some-timestamp"
  //   "severity": "INFO",
  //   "httpRequest": {
  //     "requestMethod": "GET",
  //     "requestUrl": "/some/url/from/request"
  //    },
  //   "structuredLog": {
  //      "service": "request_handlers",
  //      "handler": "handle_request"
  //    },
  //   "message": "Request received"
  // }
}
```
*/
mod google;
mod layer;
mod visitor;

pub use self::google::*;
pub use self::layer::*;