honojs-middleware/packages/otel/src/index.test.ts

91 lines
3.2 KiB
TypeScript

import { SpanKind, SpanStatusCode } from '@opentelemetry/api'
import { InMemorySpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'
import {
ATTR_HTTP_REQUEST_METHOD,
ATTR_HTTP_RESPONSE_HEADER,
ATTR_HTTP_RESPONSE_STATUS_CODE,
ATTR_URL_FULL,
ATTR_HTTP_ROUTE,
} from '@opentelemetry/semantic-conventions'
import { Hono } from 'hono'
import { otel } from '.'
describe('OpenTelemetry middleware', () => {
const app = new Hono()
const memoryExporter = new InMemorySpanExporter()
const spanProcessor = new SimpleSpanProcessor(memoryExporter)
const tracerProvider = new NodeTracerProvider({
spanProcessors: [spanProcessor],
})
app.use(otel({ tracerProvider }))
app.get('/foo', (c) => c.text('foo'))
app.post('/error', (_) => {
throw new Error('error message')
})
const subapp = new Hono()
subapp.get('/hello', (c) => c.text('Hello from subapp!'))
subapp.get('*', (c) => c.text('Fallthrough'))
// mount subapp
app.route('/subapp', subapp)
it('Should make a span', async () => {
memoryExporter.reset()
const response = await app.request('http://localhost/foo')
const spans = memoryExporter.getFinishedSpans()
expect(spans.length).toBe(1)
const [span] = spans
expect(span.name).toBe('GET /foo')
expect(span.kind).toBe(SpanKind.SERVER)
expect(span.status.code).toBe(SpanStatusCode.UNSET)
expect(span.status.message).toBeUndefined()
expect(span.attributes[ATTR_HTTP_REQUEST_METHOD]).toBe('GET')
expect(span.attributes[ATTR_URL_FULL]).toBe('http://localhost/foo')
expect(span.attributes[ATTR_HTTP_ROUTE]).toBe('/foo')
expect(span.attributes[ATTR_HTTP_RESPONSE_STATUS_CODE]).toBe(200)
for (const [name, value] of response.headers.entries()) {
expect(span.attributes[ATTR_HTTP_RESPONSE_HEADER(name)]).toBe(value)
}
})
it('Should make a span with error', async () => {
memoryExporter.reset()
await app.request('http://localhost/error', { method: 'POST' })
const spans = memoryExporter.getFinishedSpans()
expect(spans.length).toBe(1)
const [span] = spans
expect(span.name).toBe('POST /error')
expect(span.kind).toBe(SpanKind.SERVER)
expect(span.status.code).toBe(SpanStatusCode.ERROR)
expect(span.status.message).toBe('Error: error message')
expect(span.attributes[ATTR_HTTP_REQUEST_METHOD]).toBe('POST')
expect(span.attributes[ATTR_URL_FULL]).toBe('http://localhost/error')
expect(span.attributes[ATTR_HTTP_ROUTE]).toBe('/error')
})
it('Should update the active span', async () => {
memoryExporter.reset()
await tracerProvider.getTracer('test').startActiveSpan('existing span', async () => {
await app.request('http://localhost/foo')
})
const spans = memoryExporter.getFinishedSpans()
expect(spans.length).toBe(1)
const [span] = spans
expect(span.name).toBe('GET /foo')
expect(span.attributes[ATTR_HTTP_ROUTE]).toBe('/foo')
})
// Issue #1112
it('Should set the correct span name for subapp', async () => {
memoryExporter.reset()
await app.request('http://localhost/subapp/hello')
const spans = memoryExporter.getFinishedSpans()
const [span] = spans
expect(span.name).toBe('GET /subapp/hello')
})
})