Open Telemetry Component for Midway.js
ESM build only, requires @midwayjs >= 3.16
and set "type": "module"
in packages.json
- HTTP
- gRPC (Unary)
npm i @mwcp/otel
Update project src/configuration.ts
import { Configuration } from '@midwayjs/decorator'
import * as koa from '@midwayjs/koa'
import * as otel from '@mwcp/otel'
@Configuration({
imports: [
koa,
otel,
],
importConfigs: [join(__dirname, 'config')],
})
export class ContainerConfiguration implements ILifeCycle {
}
To try out the OTLPTraceExporter quickly, you can run Jaeger in a docker container:
docker run -d --name jaeger \
-e COLLECTOR_OTLP_ENABLED=true \
-p 4317:4317 \
-p 4318:4318 \
-p 5778:5778 \
-p 6831:6831/udp \
-p 6832:6832/udp \
-p 16686:16686 \
jaegertracing/all-in-one:latest
Start project:
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
npm start
Jaeger Web UI address:
import { Trace } from '@mwcp/otel'
@Controller('/')
export class FooController {
@Inject() readonly svc: FooService
/* span name will be `{class name}/{method name}` => "FooController/hello" */
@Trace()
async hello(): Promise<string> {
return 'hello'
}
/* span name will be "hello" */
@Trace('hello')
async world(): Promise<string> {
return 'world'
}
@Trace({
spanName: 'hello'
})
async world2(): Promise<string> {
return 'world'
}
}
Pass scope
to avoid the confusion of call chain relationship when async methods are called concurrently
import { Trace } from '@mwcp/otel'
@Controller('/')
export class FooController {
@Trace()
async hello(): Promise<string> {
await Promise.all([
this._simple1(),
this._simple2(),
])
return 'OK'
}
@Trace({ scope: 'hello1' })
async _hello1(): Promise<string> {
return 'world'
}
@Trace({ scope: 'hello2' })
async _hello2(): Promise<string> {
return 'world'
}
@Trace({ scope: 'hello1' })
async _hello1a(): Promise<string> {
return 'world'
}
@Trace({ scope: 'hello2' })
async _hello2a(): Promise<string> {
return 'world'
}
}
Use this
inner before()
after()
point to the decorated instance
export class FooService {
foo = 1
@Trace<Foo['home']>({
before([options], decoratorContext) {
assert(this instanceof FooService) // <--- this point to FooService
assert(this === decoratorContext.instance)
assert(this.foo === 1)
return void 0
},
after([options], res, decoratorContext) {
assert(this instanceof FooService)
assert(this === decoratorContext.instance)
assert(this.foo === 1)
return void 0
},
})
async home(this: FooService, options: InputOptions): Promise<string> { // <--- pass this type explicitly
const ret = await options.input
return ret
}
}
Add trace attribute to the span through decorator before()/after() method return object, no new span starting
- add trace tag/log to current active span
- add trace tag/log to root span
Note return value of decorated method before()
and after()
should be type:
interface DecoratorTraceData {
attrs?: Attributes
events?: Attributes
rootAttrs?: Attributes
rootEvents?: Attributes
}
import { TraceLog, DecoratorTraceData } from '@mwcp/otel'
@Controller('/')
export class FooController {
@Trace()
async hello(): Promise<string> {
return 'hello'
}
@TraceLog({
before: async ([input], { instanceName, methodName }) => {
const attrs: Attributes = {
args0: input,
}
const events: Attributes = {
...attrs,
instanceName,
methodName,
}
const rootAttrs: Attributes = { rootAttrs: 'rootAttrs' }
const rootEvents: Attributes = { ...rootAttrs }
return { attrs, events, rootAttrs, rootEvents } as DecoratorTraceData
},
after: ([input], res, { instanceName, methodName }) => {
const attrs: Attributes = {
args0: input,
res,
}
const events: Attributes = {
...attrs,
instanceName,
methodName,
}
return { events }
},
})
async world(): Promise<string> {
return 'world'
}
}
// src/configuration.ts
import { TraceInit } from '@mwcp/otel'
export class AutoConfiguration implements ILifeCycle {
@TraceInit({ namespace: 'Foo' })
async onReady(container: IMidwayContainer): Promise<void> {
// some code
}
}
@Controller('/')
export class FooController {
@Inject() readonly svc: FooService
hello(): string {
// spanName should be 'foo-124-abc'
const msg = this.svc.concat(123, 'abc')
return msg
}
}
@Provide()
export class FooService {
@Trace<FooService['concat']>({
spanName: ([v1, v2]) => `foo-${v1 + 1}-${v2}`,
})
concat(v1: number, v2: string): string {
return `${v1.toString()}-${v2}`
}
@Trace<FooService['concat2']>({
spanName: (args) => `foo-${args[0] + 1}-${args[1]}`,
})
concat2(v1: number, v2: string): string {
return `${v1.toString()}-${v2}`
}
}