Skip to content

Commit

Permalink
feat(instrumentation-bunyan): add log sending to Logs Bridge API
Browse files Browse the repository at this point in the history
This extends the Bunyan instrumentation to automatically add a
Bunyan stream to created loggers that will send log records to
the Logs Bridge API: https://opentelemetry.io/docs/specs/otel/logs/bridge-api/

Now that the instrumentation supports separate "injection" of fields
and "bridging" of log records functionality, this also adds two boolean
options to disable those independently: `enableInjection` and
`enableLogsBridge`.

This also updates the instrumentation to work with ES module usage.

Closes: open-telemetry#1559
  • Loading branch information
trentm committed Oct 3, 2023
1 parent 4503d3e commit 59f11f3
Show file tree
Hide file tree
Showing 11 changed files with 747 additions and 76 deletions.
70 changes: 49 additions & 21 deletions plugins/node/opentelemetry-instrumentation-bunyan/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[![NPM Published Version][npm-img]][npm-url]
[![Apache License][license-image]][license-image]

This module provides automatic instrumentation for the injection of trace context to [`bunyan`](https://www.npmjs.com/package/bunyan), which may be loaded using the [`@opentelemetry/sdk-trace-node`](https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-sdk-trace-node) package and is included in the [`@opentelemetry/auto-instrumentations-node`](https://www.npmjs.com/package/@opentelemetry/auto-instrumentations-node) bundle.
This module provides automatic instrumentation of the [`bunyan`](https://www.npmjs.com/package/bunyan) module to inject trace-context into Bunyan log records and to bridge Bunyan logging to the OpenTelemetry Logging SDK. It may be loaded using the [`@opentelemetry/sdk-trace-node`](https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-sdk-trace-node) package and is included in the [`@opentelemetry/auto-instrumentations-node`](https://www.npmjs.com/package/@opentelemetry/auto-instrumentations-node) bundle.

If total installation size is not constrained, it is recommended to use the [`@opentelemetry/auto-instrumentations-node`](https://www.npmjs.com/package/@opentelemetry/auto-instrumentations-node) bundle with [@opentelemetry/sdk-node](`https://www.npmjs.com/package/@opentelemetry/sdk-node`) for the most seamless instrumentation experience.

Expand All @@ -15,46 +15,74 @@ Compatible with OpenTelemetry JS API and SDK `1.0+`.
npm install --save @opentelemetry/instrumentation-bunyan
```

### Supported Versions
## Supported Versions

- `^1.0.0`

## Usage

```js
const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');
const { NodeSDK, tracing, logs, api } = require('@opentelemetry/sdk-node');
const { BunyanInstrumentation } = require('@opentelemetry/instrumentation-bunyan');
const { registerInstrumentations } = require('@opentelemetry/instrumentation');

const provider = new NodeTracerProvider();
provider.register();

registerInstrumentations({
const sdk = new NodeSDK({
spanProcessor: new tracing.SimpleSpanProcessor(new tracing.ConsoleSpanExporter()),
logRecordProcessor: new logs.SimpleLogRecordProcessor(new logs.ConsoleLogRecordExporter()),
instrumentations: [
new BunyanInstrumentation({
// Optional hook to insert additional context to bunyan records.
// Called after trace context is added to the record.
logHook: (span, record) => {
record['resource.service.name'] = provider.resource.attributes['service.name'];
},
// See below for Bunyan instrumentation options.
}),
// other instrumentations
],
});
]
})

const log = bunyan.createLogger({name: 'example'});

log.info('hi');
// 1. Log records will be sent to the SDK-registered log record processor, if any.
// This is called "bridging".

bunyan.createLogger({ name: 'example' }).info('foo');
// {"name":"example","msg":"foo","trace_id":"e21c7a95fff34e04f77c7bd518779621","span_id":"b7589a981fde09f4","trace_flags":"01", ...}
const tracer = api.getTracer('example');
tracer.startActiveSpan('manual-span', span => {
log.info('in a span');
// 2. Fields identifying the current span will be injected into log records:
// {"name":"example",...,"msg":"in a span","trace_id":"d61b4e4af1032e0aae279d12f3ab0159","span_id":"d140da862204f2a2","trace_flags":"01"}
})
```

### Fields added to bunyan records
### Logs Bridge

Creation of a Bunyan Logger will automatically add a [Bunyan stream](https://github.com/trentm/node-bunyan#streams) that sends log records to the OpenTelemetry Logs Bridge API. The OpenTelemetry SDK can be configured to handle those records -- for example, sending them on to an OpenTelemetry collector for log archiving and processing. The example above shows a minimal configuration that emits OpenTelemetry log records to the console for debugging.

If the OpenTelemetry SDK is not configured with a Logger provider, then this added stream will be a no-op.

The logs bridge can be disabled with the `enableLogsBridge: false` option.

For the current active span, the following will be added to the bunyan record:
### Log injection

Bunyan logger calls in the context of a tracing span will have fields indentifying
the span ([spec](https://opentelemetry.io/docs/specs/otel/compatibility/logging_trace_context/)):

- `trace_id`
- `span_id`
- `trace_flags`

After adding these fields, the optional `logHook` is called to allow injecting additional fields. For example:

```js
logHook: (span, record) => {
record['resource.service.name'] = provider.resource.attributes['service.name'];
}
```

When no span context is active or the span context is invalid, injection is skipped.
Log injection can be disabled with the `enableInjection: false` option.

### Bunyan instrumentation options

| Option | Type | Description |
| ------------------ | ----------------- | ----------- |
| `enableLogsBridge` | `boolean` | Whether [logs bridging](#logs-bridge) is enabled. Default `true`. |
| `enableInjection` | `boolean` | Whether [log injection](#log-injection) is enabled. Default `true`. |
| `logHook` | `LogHookFunction` | An option hook to inject additional context to a log record after span context has been added. This requires `enableInjection` to be true. |

## Useful links

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
This is a small example app, "app.js", that shows using the
[Bunyan](https://github.com/trentm/node-bunyan) logger with OpenTelemetry. See
[the OpenTelemetry Bunyan instrumentation README](../) for full details.

# Usage

```
npm install
node -r ./telemetry.js app.js
```

# Overview

"telemetry.js" sets up the OpenTelemetry SDK to write OTel tracing spans and
log records to the *console* for simplicity. In a real setup you would
configure exporters to send to remote observability apps for viewing and
analysis.

An example run looks like this:

```
$ node -r ./telemetry.js app.js
{"name":"myapp","hostname":"amachine.local","pid":93017,"level":20,"msg":"hi","time":"2023-09-27T23:24:06.074Z","v":0}
{
timestamp: 1695857046074000,
traceId: undefined,
spanId: undefined,
traceFlags: undefined,
severityText: 'debug',
severityNumber: 5,
body: 'hi',
attributes: { name: 'myapp', hostname: 'amachine.local', pid: 93017 }
}
{"name":"myapp","hostname":"amachine.local","pid":93017,"level":30,"msg":"this record will have trace_id et al fields for the current span","time":"2023-09-27T23:24:06.079Z","v":0,"trace_id":"af5ce23816c4feabb713ee1dc84ef4d3","span_id":"5f50e181ec7bc621","trace_flags":"01"}
{
timestamp: 1695857046079000,
traceId: 'af5ce23816c4feabb713ee1dc84ef4d3',
spanId: '5f50e181ec7bc621',
traceFlags: 1,
severityText: 'info',
severityNumber: 9,
body: 'this record will have trace_id et al fields for the current span',
attributes: {
name: 'myapp',
hostname: 'amachine.local',
pid: 93017,
trace_id: 'af5ce23816c4feabb713ee1dc84ef4d3',
span_id: '5f50e181ec7bc621',
trace_flags: '01'
}
}
{
traceId: 'af5ce23816c4feabb713ee1dc84ef4d3',
parentId: undefined,
traceState: undefined,
name: 'manual-span',
id: '5f50e181ec7bc621',
kind: 0,
timestamp: 1695857046079000,
duration: 1407.196,
attributes: {},
status: { code: 0 },
events: [],
links: []
}
```

There are two separate Bunyan instrumentation functionalities. The first is
that Bunyan log records emitted in the context of a tracing span will include
`trace_id` and `span_id` fields that can be used for correlating with collect
tracing data. Line 3 shows an example of this.

The second is that a [Bunyan
stream](https://github.com/trentm/node-bunyan#streams) is automatically added
to created Loggers that will send every log record to the OpenTelemetry Logs
Bridge API. This means that if the OpenTelemetry SDK has been configured with
a Logger Provider, it will receive them. (If the OpenTelemetry SDK is not
configured for this, then the added Bunyan stream will be a no-op.) Lines 2
and 4 show a dumped OpenTelemetry Log Record.

32 changes: 32 additions & 0 deletions plugins/node/opentelemetry-instrumentation-bunyan/examples/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

// A small example that shows using OpenTelemetry's instrumentation of
// Bunyan loggers. Usage:
// node --require ./telemetry.js app.js

const otel = require('@opentelemetry/api');
const bunyan = require('bunyan');

const log = bunyan.createLogger({name: 'myapp', level: 'debug'});

log.debug('hi');

const tracer = otel.trace.getTracer('example');
tracer.startActiveSpan('manual-span', span => {
log.info('this record will have trace_id et al fields for the current span');
span.end();
});
33 changes: 33 additions & 0 deletions plugins/node/opentelemetry-instrumentation-bunyan/examples/app.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

// The equivalent of "app.js", but showing usage with ESM code.
// Usage:
// node --require ./telemetry.js --experimental-loader ../node_modules/@opentelemetry/instrumentation/hook.mjs app.js

import { trace } from '@opentelemetry/api';
import bunyan from 'bunyan';

const log = bunyan.createLogger({name: 'myapp', level: 'debug'});

log.debug('hi');

const tracer = trace.getTracer('example');
tracer.startActiveSpan('manual-span', span => {
log.info('this record will have trace_id et al fields for the current span');
span.end();
});

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "bunyan-example",
"private": true,
"version": "0.43.0",
"description": "Example of Bunyan integration with OpenTelemetry",
"scripts": {
"start": "node -r ./telemetry.js app.js"
},
"repository": {
"type": "git",
"url": "git+ssh://git@github.com/open-telemetry/opentelemetry-js.git"
},
"engines": {
"node": ">=14"
},
"author": "OpenTelemetry Authors",
"license": "Apache-2.0",
"// @opentelemetry/instrumentation-bunyan": "TODO: change to a ver when there is a next release",
"dependencies": {
"@opentelemetry/api": "^1.6.0",
"@opentelemetry/instrumentation-bunyan": "../",
"@opentelemetry/sdk-node": "^0.43.0",
"bunyan": "^1.8.15"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

// Setup telemetry for tracing and Bunyan logging.
//
// This writes OTel spans and log records to the console for simplicity. In a
// real setup you would configure exporters to send to remote observability apps
// for viewing and analysis.

const { NodeSDK, tracing, logs, api } = require('@opentelemetry/sdk-node');
// api.diag.setLogger(new api.DiagConsoleLogger(), api.DiagLogLevel.DEBUG);

const { BunyanInstrumentation } = require('@opentelemetry/instrumentation-bunyan');

const sdk = new NodeSDK({
serviceName: 'bunyan-example',
spanProcessor: new tracing.SimpleSpanProcessor(new tracing.ConsoleSpanExporter()),
logRecordProcessor: new logs.SimpleLogRecordProcessor(new logs.ConsoleLogRecordExporter()),
instrumentations: [
new BunyanInstrumentation(),
]
})
process.on("SIGTERM", () => {
sdk
.shutdown()
.then(
() => {},
(err) => console.log("warning: error shutting down OTel SDK", err)
)
.finally(() => process.exit(0));
});
sdk.start();
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,11 @@
},
"devDependencies": {
"@opentelemetry/api": "^1.3.0",
"@opentelemetry/context-async-hooks": "^1.8.0",
"@opentelemetry/resources": "^1.17.0",
"@opentelemetry/sdk-logs": "^0.43.0",
"@opentelemetry/sdk-trace-base": "^1.8.0",
"@opentelemetry/sdk-trace-node": "^1.8.0",
"@opentelemetry/semantic-conventions": "^1.17.0",
"@types/mocha": "7.0.2",
"@types/node": "18.6.5",
"@types/sinon": "10.0.16",
Expand All @@ -64,6 +66,7 @@
"typescript": "4.4.4"
},
"dependencies": {
"@opentelemetry/api-logs": "^0.43.0",
"@opentelemetry/instrumentation": "^0.43.0",
"@types/bunyan": "1.8.9"
},
Expand Down
Loading

0 comments on commit 59f11f3

Please sign in to comment.