Compare commits

...

4 Commits

Author SHA1 Message Date
Mohit Khatri 73048ba9d4
Merge f46705474f into ca3cada076 2025-04-10 20:32:47 +10:00
github-actions[bot] ca3cada076
Version Packages (#1118)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-10 17:47:54 +09:00
Milo Hansen 362b6701a6
fix(otel): Use `req.routePath` when tagging spans (#1113)
* use routePath

Replace indexing into the matchedRoutes with a direct reference to routePath

* Update package.json

Bump version

* set span name and attributes after the request is handled

* revert version bump

* changeset

* add test to ensure subapps set the correct span name

---------

Co-authored-by: Milo Hansen <milo.hansen@avanade.com>
2025-04-10 17:40:54 +09:00
Mohit Khari f46705474f update README.md to include middleware usage details 2024-06-08 16:30:30 +05:30
5 changed files with 118 additions and 15 deletions

View File

@ -117,6 +117,81 @@ const useSession = () => {
For more details on how to Popup Oauth Login see [example](https://github.com/divyam234/next-auth-hono-react)
## Middleware
You can separate this code into another file, say `auth.config.ts`:
```ts
function getAuthConfig(c: Context): AuthConfig {
return {
secret: c.env.AUTH_SECRET,
providers: [
GitHub({
clientId: c.env.GITHUB_ID,
clientSecret: c.env.GITHUB_SECRET,
}),
],
}
}
```
Use the same config in `middleware.ts`
```ts
import { getAuthConfig } from '@/auth.config'
import { getAuthUser, initAuthConfig } from '@hono/auth-js'
import { Hono } from 'hono'
import { handle } from 'hono/vercel'
import { NextResponse } from 'next/server'
const app = new Hono()
// shared config
app.use('*', initAuthConfig(getAuthConfig))
app.all('*', async (c) => {
// Retrieve the user & session
const authUser = await getAuthUser(c)
const pathname = new URL(c.req.url).pathname
const isAuthenticated = !!authUser?.session
// Specific to Auth.js (may vary if customized)
const isApiAuthRoute = pathname.startsWith('/api/auth')
const isPublicRoute = ['/'].includes(pathname)
const isAuthRoute = ['/sign-in'].includes(pathname)
if (isApiAuthRoute) return NextResponse.next()
if (isAuthRoute) {
if (isAuthenticated) {
return Response.redirect(new URL('/protected', c.req.url))
}
return NextResponse.next()
}
if (!isAuthenticated && !isPublicRoute) {
return Response.redirect(new URL('/sign-in', c.req.url))
}
return NextResponse.next()
})
export default handle(app)
export const config = {
matcher: ['/((?!.+\\.[\\w]+$|_next).*)', '/', '/(api|trpc)(.*)'],
}
```
Middleware setup repo: https://github.com/mohit4bug/nextjs-hono-authjs
## Author
Divyam <https://github.com/divyam234>
## Contributors
- Mohit <https://github.com/mohit4bug>
- Updated the README.md to include additional details about using middleware.

View File

@ -1,5 +1,11 @@
# @hono/otel
## 0.1.1
### Patch Changes
- [#1113](https://github.com/honojs/middleware/pull/1113) [`362b6701a6ee2843a51c1dfd5877a6164b7474fb`](https://github.com/honojs/middleware/commit/362b6701a6ee2843a51c1dfd5877a6164b7474fb) Thanks [@milohansen](https://github.com/milohansen)! - Use `req.routePath` to augment spans with the path that handled the request.
## 0.1.0
### Minor Changes

View File

@ -1,6 +1,6 @@
{
"name": "@hono/otel",
"version": "0.1.0",
"version": "0.1.1",
"description": "OpenTelemetry middleware for Hono",
"type": "module",
"module": "dist/index.js",

View File

@ -26,6 +26,13 @@ describe('OpenTelemetry middleware', () => {
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')
@ -71,4 +78,13 @@ describe('OpenTelemetry middleware', () => {
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')
})
})

View File

@ -1,5 +1,5 @@
import type { TracerProvider } from '@opentelemetry/api'
import { SpanKind, SpanStatusCode, trace } from '@opentelemetry/api'
import { SpanKind, SpanStatusCode, trace } from '@opentelemetry/api'
import {
ATTR_HTTP_REQUEST_HEADER,
ATTR_HTTP_REQUEST_METHOD,
@ -10,17 +10,19 @@ import {
} from '@opentelemetry/semantic-conventions'
import type { Env, Input } from 'hono'
import { createMiddleware } from 'hono/factory'
import metadata from '../package.json' with { type: 'json'}
import metadata from '../package.json' with { type: 'json' }
const PACKAGE_NAME = metadata.name
const PACKAGE_VERSION = metadata.version
export type OtelOptions = {
augmentSpan?: false;
tracerProvider?: TracerProvider
} | {
augmentSpan: true;
}
export type OtelOptions =
| {
augmentSpan?: false
tracerProvider?: TracerProvider
}
| {
augmentSpan: true
}
export const otel = <E extends Env = any, P extends string = any, I extends Input = {}>(
options: OtelOptions = {}
@ -30,9 +32,9 @@ export const otel = <E extends Env = any, P extends string = any, I extends Inpu
const result = await next()
const span = trace.getActiveSpan()
if (span != null) {
const route = c.req.matchedRoutes[c.req.matchedRoutes.length - 1]
span.setAttribute(ATTR_HTTP_ROUTE, route.path)
span.updateName(`${c.req.method} ${route.path}`)
const routePath = c.req.routePath
span.setAttribute(ATTR_HTTP_ROUTE, routePath)
span.updateName(`${c.req.method} ${routePath}`)
}
return result
})
@ -40,15 +42,15 @@ export const otel = <E extends Env = any, P extends string = any, I extends Inpu
const tracerProvider = options.tracerProvider ?? trace.getTracerProvider()
const tracer = tracerProvider.getTracer(PACKAGE_NAME, PACKAGE_VERSION)
return createMiddleware<E, P, I>(async (c, next) => {
const route = c.req.matchedRoutes[c.req.matchedRoutes.length - 1]
const routePath = c.req.routePath
await tracer.startActiveSpan(
`${c.req.method} ${route.path}`,
`${c.req.method} ${c.req.routePath}`,
{
kind: SpanKind.SERVER,
attributes: {
[ATTR_HTTP_REQUEST_METHOD]: c.req.method,
[ATTR_URL_FULL]: c.req.url,
[ATTR_HTTP_ROUTE]: route.path,
[ATTR_HTTP_ROUTE]: routePath,
},
},
async (span) => {
@ -57,6 +59,10 @@ export const otel = <E extends Env = any, P extends string = any, I extends Inpu
}
try {
await next()
// Update the span name and route path now that we have the response
// because the route path may have changed
span.updateName(`${c.req.method} ${c.req.routePath}`)
span.setAttribute(ATTR_HTTP_ROUTE, c.req.routePath)
span.setAttribute(ATTR_HTTP_RESPONSE_STATUS_CODE, c.res.status)
for (const [name, value] of c.res.headers.entries()) {
span.setAttribute(ATTR_HTTP_RESPONSE_HEADER(name), value)