Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions dev-packages/node-integration-tests/suites/tracing/hapi/test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { afterAll, describe, expect } from 'vitest';
import { isOrchestrionEnabled } from '../../../utils';
import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner';

describe('hapi auto-instrumentation', () => {
afterAll(async () => {
cleanupChildProcesses();
});

// `createEsmAndCjsTests` auto-runs this suite with orchestrion on CI. The
// orchestrion path keeps span ops/attributes identical to the OTel path; only
// the origin differs to signal the injection mechanism, so we branch on
// `isOrchestrionEnabled()`.
const origin = isOrchestrionEnabled() ? 'auto.http.orchestrion.hapi' : 'auto.http.otel.hapi';

const EXPECTED_TRANSACTION = {
transaction: 'GET /',
spans: expect.arrayContaining([
Expand All @@ -14,12 +21,12 @@ describe('hapi auto-instrumentation', () => {
'http.route': '/',
'http.method': 'GET',
'hapi.type': 'router',
'sentry.origin': 'auto.http.otel.hapi',
'sentry.origin': origin,
'sentry.op': 'router.hapi',
}),
description: 'GET /',
op: 'router.hapi',
origin: 'auto.http.otel.hapi',
origin,
status: 'ok',
}),
]),
Expand Down Expand Up @@ -52,24 +59,24 @@ describe('hapi auto-instrumentation', () => {
expect.objectContaining({
description: 'GET /plugin-route',
op: 'plugin.hapi',
origin: 'auto.http.otel.hapi',
origin,
data: expect.objectContaining({
'http.route': '/plugin-route',
'hapi.type': 'plugin',
'hapi.plugin.name': 'testPlugin',
'sentry.op': 'plugin.hapi',
'sentry.origin': 'auto.http.otel.hapi',
'sentry.origin': origin,
}),
}),
expect.objectContaining({
description: 'ext - onPreResponse',
op: 'server.ext.hapi',
origin: 'auto.http.otel.hapi',
origin,
data: expect.objectContaining({
'hapi.type': 'server.ext',
'server.ext.type': 'onPreResponse',
'sentry.op': 'server.ext.hapi',
'sentry.origin': 'auto.http.otel.hapi',
'sentry.origin': origin,
}),
}),
]),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
mysqlChannelIntegration,
lruMemoizerChannelIntegration,
hapiChannelIntegration,
detectOrchestrionSetup,
} from '@sentry/server-utils/orchestrion';
import { registerDiagnosticsChannelInjection } from '@sentry/server-utils/orchestrion/register';
Expand Down Expand Up @@ -41,7 +42,11 @@ import { setDiagnosticsChannelInjectionLoader } from './diagnosticsChannelInject
*/
export function experimentalUseDiagnosticsChannelInjection(): void {
setDiagnosticsChannelInjectionLoader((): DiagnosticsChannelInjection => {
const integrations = [mysqlChannelIntegration(), lruMemoizerChannelIntegration()] as const;
const integrations = [
mysqlChannelIntegration(),
lruMemoizerChannelIntegration(),
hapiChannelIntegration(),
] as const;
const replacedOtelIntegrationNames = integrations.map(i => i.name);

return {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Structural type definitions and constants ported from the vendored
* `@opentelemetry/instrumentation-hapi` types, with all `@hapi/*` and
* `@opentelemetry/*` dependencies removed. Only the shapes actually accessed by
* the orchestrion hapi subscriber are kept.
*/

// Single source of truth for the request lifecycle extension points, so the
// `ServerRequestExtType` union and the runtime `HapiLifecycleMethodNames` set
// below can't drift apart.
const LIFECYCLE_EXT_POINTS = [
'onPreAuth',
'onCredentials',
'onPostAuth',
'onPreHandler',
'onPostHandler',
'onPreResponse',
'onRequest',
] as const;

export type ServerRequestExtType = (typeof LIFECYCLE_EXT_POINTS)[number];

export type LifecycleMethod = (request: unknown, h: unknown, err?: Error) => unknown;

export interface ServerRouteOptions {
handler?: LifecycleMethod | unknown;
[key: string]: unknown;
}

export interface ServerRoute {
path: string;
method: string;
handler?: LifecycleMethod | unknown;
options?: ((server: unknown) => ServerRouteOptions) | ServerRouteOptions;
[key: string]: unknown;
}

export interface ServerExtEventsObject {
type: string;
[key: string]: unknown;
}

export interface ServerExtEventsRequestObject {
type: ServerRequestExtType;
method: LifecycleMethod;
[key: string]: unknown;
}

export interface ServerExtOptions {
[key: string]: unknown;
}

/**
* This symbol is used to mark a Hapi route handler or server extension handler as
* already patched, since it's possible to use these handlers multiple times
* i.e. when allowing multiple versions of one plugin, or when registering a plugin
* multiple times on different servers.
*/
export const handlerPatched: unique symbol = Symbol('hapi-handler-patched');

export type PatchableServerRoute = ServerRoute & {
[handlerPatched]?: boolean;
};

export type PatchableExtMethod = LifecycleMethod & {
[handlerPatched]?: boolean;
};

export type ServerExtDirectInput = [ServerRequestExtType, LifecycleMethod, (ServerExtOptions | undefined)?];

export const HapiLayerType = {
ROUTER: 'router',
PLUGIN: 'plugin',
EXT: 'server.ext',
} as const;

export const HapiLifecycleMethodNames = new Set<string>(LIFECYCLE_EXT_POINTS);

export enum AttributeNames {
HAPI_TYPE = 'hapi.type',
PLUGIN_NAME = 'hapi.plugin.name',
EXT_TYPE = 'server.ext.type',
}
Loading
Loading