This guide will help you migrate your application from @amplication/opentelemetry-nestjs v5.x to v6.x.
Version 6.0 introduces significant breaking changes to improve type safety, align with OpenTelemetry standards, and enhance code organization. The changes affect:
- Module configuration structure
- SDK initialization
- Import paths
- Custom instrumentation classes
- Async configuration
Estimated migration time: 30-60 minutes per project
- SDK Initialization
- Module Configuration
- Import Path Updates
- Custom Instrumentation
- Async Configuration
- Type Changes
- Complete Migration Example
// main.ts - at the very top
import { Tracing } from '@amplication/opentelemetry-nestjs';
import { ZipkinExporter } from '@opentelemetry/exporter-zipkin';
import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node';
Tracing.init({
serviceName: 'my-service',
spanProcessor: new SimpleSpanProcessor(
new ZipkinExporter({
url: 'your-zipkin-url',
}),
),
});
import { NestFactory } from '@nestjs/core';
// ...// tracing.ts (or similar)
import { startNestJsOpenTelemetrySDK } from '@amplication/opentelemetry-nestjs';
import { ZipkinExporter } from '@opentelemetry/exporter-zipkin';
import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node';
startNestJsOpenTelemetrySDK({
serviceName: 'my-service',
spanProcessors: [
new SimpleSpanProcessor(
new ZipkinExporter({
url: 'your-zipkin-url',
}),
),
],
});// main.ts - at the very top
import './tracing';
import { NestFactory } from '@nestjs/core';
// ...Key Changes:
Tracing.init()→startNestJsOpenTelemetrySDK()spanProcessor→spanProcessors(now an array)- SDK initialization is now separate from the NestJS module
import { OpenTelemetryModule } from '@amplication/opentelemetry-nestjs';
@Module({
imports: [
OpenTelemetryModule.forRoot({
serviceName: 'nestjs-opentelemetry-example',
}),
],
})
export class AppModule {}Note: In v5.x, forRoot() could accept an array of injectors or use defaults.
import {
OpenTelemetryModule,
ControllerInjector,
GuardInjector,
} from '@amplication/opentelemetry-nestjs';
@Module({
imports: [OpenTelemetryModule.forRoot([ControllerInjector, GuardInjector])],
})
export class AppModule {}import { OpenTelemetryModule } from '@amplication/opentelemetry-nestjs';
@Module({
imports: [OpenTelemetryModule.forRoot()],
})
export class AppModule {}With custom instrumentation:
import {
OpenTelemetryModule,
ControllerInstrumentation,
GuardInstrumentation,
} from '@amplication/opentelemetry-nestjs';
@Module({
imports: [
OpenTelemetryModule.forRoot({
instrumentation: [ControllerInstrumentation, GuardInstrumentation],
}),
],
})
export class AppModule {}Key Changes:
- Configuration is now an object with an
instrumentationproperty serviceNameis no longer part of module config (moved to SDK initialization)- All
*Injectorclasses renamed to*Instrumentation
All files have been renamed from PascalCase to kebab-case:
| v5.x | v6.x |
|---|---|
OpenTelemetryModule |
open-telemetry.module |
Constants |
constants |
Tracing |
open-telemetry-nestjs-sdk |
Trace/Injectors/* |
trace/instrumentation/* |
Trace/Decorators/* |
trace/decorators/* |
import {
OpenTelemetryModule,
ControllerInjector,
GuardInjector,
EventEmitterInjector,
ScheduleInjector,
PipeInjector,
ConsoleLoggerInjector,
GraphQLResolverInjector,
} from '@amplication/opentelemetry-nestjs';import {
OpenTelemetryModule,
ControllerInstrumentation,
GuardInstrumentation,
EventEmitterInstrumentation,
ScheduleInstrumentation,
PipeInstrumentation,
ConsoleLoggerInstrumentation,
GraphQLResolverInstrumentation,
} from '@amplication/opentelemetry-nestjs';Note: The main package exports remain the same, but internal class names have changed.
If you created custom injectors, you'll need to update them to the new Instrumentation interface.
import { Injectable } from '@nestjs/common';
import { Injector } from '@amplication/opentelemetry-nestjs';
@Injectable()
export class MyCustomInjector implements Injector {
async inject() {
// Your instrumentation logic
}
}import { Injectable } from '@nestjs/common';
import { Instrumentation } from '@amplication/opentelemetry-nestjs';
@Injectable()
export class MyCustomInstrumentation implements Instrumentation {
async setupInstrumentation() {
// Your instrumentation logic
}
}Key Changes:
Injectorinterface →Instrumentationinterfaceinject()method →setupInstrumentation()method- Class naming convention:
*Injector→*Instrumentation
import { OpenTelemetryModule } from '@amplication/opentelemetry-nestjs';
import { ConfigModule, ConfigService } from '@nestjs/config';
@Module({
imports: [
OpenTelemetryModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
serviceName: configService.get('SERVICE_NAME'),
}),
inject: [ConfigService],
}),
],
})
export class AppModule {}import { OpenTelemetryModule } from '@amplication/opentelemetry-nestjs';
import { ConfigModule, ConfigService } from '@nestjs/config';
@Module({
imports: [
OpenTelemetryModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
instrumentation: [
// Add your instrumentation here if needed
],
}),
inject: [ConfigService],
}),
],
})
export class AppModule {}Key Changes:
- Better type safety with generic type parameters
serviceNameremoved from module config (now in SDK initialization)- Configuration structure matches the synchronous
forRoot()format
Note: The async configuration now has improved type safety. TypeScript will infer the types of injected dependencies.
Before:
type OpenTelemetryModuleConfig = Provider<Injector>[];After:
type OpenTelemetryModuleConfig = {
instrumentation?: Provider<Instrumentation>[];
};Before:
interface OpenTelemetryModuleAsyncOption {
useFactory?: (
...args: any[]
) =>
| Promise<Partial<OpenTelemetryModuleConfig>>
| Partial<OpenTelemetryModuleConfig>;
inject?: any[];
}After:
interface OpenTelemetryModuleAsyncOptions<Tokens extends InjectionToken[]> {
useFactory?: (
...args: {
[K in keyof Tokens]: Tokens[K] extends InjectionToken<infer T>
? T
: never;
}
) =>
| Promise<Partial<OpenTelemetryModuleConfig>>
| Partial<OpenTelemetryModuleConfig>;
inject?: Tokens;
}Here's a complete example showing the migration of a typical application:
// main.ts
import { Tracing } from '@amplication/opentelemetry-nestjs';
import { ZipkinExporter } from '@opentelemetry/exporter-zipkin';
import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node';
Tracing.init({
serviceName: 'my-app',
spanProcessor: new SimpleSpanProcessor(
new ZipkinExporter({ url: 'http://localhost:9411/api/v2/spans' }),
),
});
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();// app.module.ts
import { Module } from '@nestjs/common';
import {
OpenTelemetryModule,
ControllerInjector,
GuardInjector,
} from '@amplication/opentelemetry-nestjs';
@Module({
imports: [OpenTelemetryModule.forRoot([ControllerInjector, GuardInjector])],
})
export class AppModule {}// tracing.ts
import { startNestJsOpenTelemetrySDK } from '@amplication/opentelemetry-nestjs';
import { ZipkinExporter } from '@opentelemetry/exporter-zipkin';
import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node';
startNestJsOpenTelemetrySDK({
serviceName: 'my-app',
spanProcessors: [
new SimpleSpanProcessor(
new ZipkinExporter({ url: 'http://localhost:9411/api/v2/spans' }),
),
],
});// main.ts
import './tracing';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();// app.module.ts
import { Module } from '@nestjs/common';
import {
OpenTelemetryModule,
ControllerInstrumentation,
GuardInstrumentation,
} from '@amplication/opentelemetry-nestjs';
@Module({
imports: [
OpenTelemetryModule.forRoot({
instrumentation: [ControllerInstrumentation, GuardInstrumentation],
}),
],
})
export class AppModule {}Use this checklist to ensure you've completed all necessary changes:
- Update SDK initialization from
Tracing.init()tostartNestJsOpenTelemetrySDK() - Move
serviceNamefrom module config to SDK initialization - Update
spanProcessortospanProcessors(array) in SDK config - Update module configuration from array to object format
- Rename all
*Injectorimports to*Instrumentation - Update custom injectors to use
Instrumentationinterface andsetupInstrumentation()method - Update async configuration if used
- Test all instrumentation is working correctly
- Verify traces are being exported correctly
In v6, trace naming has been updated to align with OpenTelemetry Semantic Conventions:
Before (v5):
- Trace name:
Pipe->Global->MyPipe - Attributes: Basic
After (v6):
- Trace name:
MyPipe - Attributes:
nestjs.type: pipenestjs.scope: global
This provides cleaner trace names while maintaining all the necessary context through attributes.
The default instrumentation in v6 includes:
ControllerInstrumentationGraphQLResolverInstrumentationGuardInstrumentationInterceptorInstrumentation(new in v6)EventEmitterInstrumentationScheduleInstrumentationPipeInstrumentationConsoleLoggerInstrumentation
If you were using the defaults in v5, you don't need to specify them explicitly in v6.
The migration to v6.x improves:
- ✅ Type safety with better TypeScript support
- ✅ Semantic clarity with OpenTelemetry-aligned naming
- ✅ Code organization with clearer structure
- ✅ Separation of concerns (SDK vs Module)
While the changes are breaking, they result in a more maintainable and standards-compliant library.
Thanks to @Helveg for most of the modernization work that made v6.0 possible. See PR #13: Modernization of the package for details.