fix CodeRabbit review

This commit is contained in:
samunohito
2026-07-31 10:41:21 +09:00
parent 038623325c
commit 33afa9876f
13 changed files with 307 additions and 63 deletions
+1 -1
View File
@@ -71,7 +71,7 @@
"@opentelemetry/api": "1.9.1",
"@opentelemetry/core": "2.9.0",
"@opentelemetry/exporter-trace-otlp-proto": "0.220.0",
"@opentelemetry/instrumentation": "0.219.0",
"@opentelemetry/instrumentation": "0.220.0",
"@opentelemetry/instrumentation-pg": "0.72.0",
"@opentelemetry/resources": "2.9.0",
"@opentelemetry/sdk-trace-base": "2.9.0",
+13 -2
View File
@@ -27,6 +27,10 @@ type SentryBackendConfig = {
disabledIntegrations?: string[];
};
type SentryBackendConfigSource = Omit<SentryBackendConfig, 'options'> & {
options?: Partial<Sentry.NodeOptions>;
};
type OtelBackendConfig = {
endpoint?: string;
headers?: Record<string, string>;
@@ -86,7 +90,7 @@ type Source = {
index: string;
scope?: 'local' | 'global' | string[];
};
sentryForBackend?: SentryBackendConfig;
sentryForBackend?: SentryBackendConfigSource;
otelForBackend?: OtelBackendConfig;
sentryForFrontend?: {
options: Partial<SentryVue.BrowserOptions> & { dsn: string };
@@ -338,7 +342,7 @@ export function loadConfig(): Config {
redisForJobQueue: config.redisForJobQueue ? convertRedisOptions(config.redisForJobQueue, host) : redis,
redisForTimelines: config.redisForTimelines ? convertRedisOptions(config.redisForTimelines, host) : redis,
redisForReactions: config.redisForReactions ? convertRedisOptions(config.redisForReactions, host) : redis,
sentryForBackend: config.sentryForBackend,
sentryForBackend: config.sentryForBackend == null ? undefined : normalizeSentryBackendConfig(config.sentryForBackend),
otelForBackend: config.otelForBackend,
sentryForFrontend: config.sentryForFrontend,
id: config.id,
@@ -376,6 +380,13 @@ export function loadConfig(): Config {
};
}
export function normalizeSentryBackendConfig(config: SentryBackendConfigSource): SentryBackendConfig {
return {
...config,
options: config.options ?? {},
};
}
function tryCreateUrl(url: string) {
try {
return new URL(url);
@@ -4,6 +4,7 @@
*/
import { channel } from 'node:diagnostics_channel';
import { diag } from '@opentelemetry/api';
import type { ClientRequest, IncomingMessage } from 'node:http';
import type { Span, SpanOptions, SpanStatusCode, Tracer } from '@opentelemetry/api';
@@ -18,6 +19,7 @@ type HttpClientInstrumentationDeps = {
spanKindClient: SpanOptions['kind'];
spanStatusCodeError: SpanStatusCode;
subscribe: (name: string, listener: (message: unknown) => void) => () => void;
reportError?: (error: unknown) => void;
};
type RequestCreatedMessage = { request: ClientRequest };
@@ -31,10 +33,10 @@ type RequestErrorMessage = { request: ClientRequest; error: Error };
export function createHttpClientInstrumentation(deps: HttpClientInstrumentationDeps): () => void {
const spans = new WeakMap<ClientRequest, HttpClientSpan>();
const unsubscribeCreated = deps.subscribe(HTTP_CLIENT_REQUEST_CREATED, (message: unknown) => {
const unsubscribeCreated = deps.subscribe(HTTP_CLIENT_REQUEST_CREATED, guardDiagnosticsListener(deps, (message: unknown) => {
const { request } = message as RequestCreatedMessage;
const { url, host, port } = getRequestDetails(request);
const method = request.method ?? 'GET';
const method = request.method;
const span = deps.tracer.startSpan(method, {
kind: deps.spanKindClient,
attributes: {
@@ -45,9 +47,9 @@ export function createHttpClientInstrumentation(deps: HttpClientInstrumentationD
},
});
spans.set(request, span);
});
}));
const unsubscribeResponseFinish = deps.subscribe(HTTP_CLIENT_RESPONSE_FINISH, (message: unknown) => {
const unsubscribeResponseFinish = deps.subscribe(HTTP_CLIENT_RESPONSE_FINISH, guardDiagnosticsListener(deps, (message: unknown) => {
const { request, response } = message as ResponseFinishMessage;
const span = spans.get(request);
if (span == null) return;
@@ -56,18 +58,16 @@ export function createHttpClientInstrumentation(deps: HttpClientInstrumentationD
if (statusCode != null) {
span.setAttribute('http.response.status_code', statusCode);
}
if (response.httpVersion != null) {
span.setAttribute('network.protocol.version', response.httpVersion);
}
span.setAttribute('network.protocol.version', response.httpVersion);
if (statusCode != null && statusCode >= 400) {
span.setAttribute('error.type', String(statusCode));
span.setStatus({ code: deps.spanStatusCodeError });
}
span.end();
spans.delete(request);
});
}));
const unsubscribeRequestError = deps.subscribe(HTTP_CLIENT_REQUEST_ERROR, (message: unknown) => {
const unsubscribeRequestError = deps.subscribe(HTTP_CLIENT_REQUEST_ERROR, guardDiagnosticsListener(deps, (message: unknown) => {
const { request, error } = message as RequestErrorMessage;
const span = spans.get(request);
if (span == null) return;
@@ -77,7 +77,7 @@ export function createHttpClientInstrumentation(deps: HttpClientInstrumentationD
span.setStatus({ code: deps.spanStatusCodeError });
span.end();
spans.delete(request);
});
}));
return () => {
unsubscribeCreated();
@@ -89,6 +89,7 @@ export function createHttpClientInstrumentation(deps: HttpClientInstrumentationD
export function installHttpClientInstrumentation(deps: Omit<HttpClientInstrumentationDeps, 'subscribe'>): () => void {
return createHttpClientInstrumentation({
...deps,
reportError: deps.reportError ?? (error => diag.error('Failed to process HTTP client diagnostics event.', error)),
subscribe: (name, listener) => {
const diagnosticChannel = channel(name);
diagnosticChannel.subscribe(listener);
@@ -98,20 +99,47 @@ export function installHttpClientInstrumentation(deps: Omit<HttpClientInstrument
}
function getRequestDetails(request: ClientRequest): { url: string; host: string; port: number } {
const protocol = request.protocol ?? 'http:';
const host = request.getHeader('host')?.toString() ?? request.host ?? 'localhost';
const url = new URL(request.path || '/', `${protocol}//${host}`);
// URL 属性には認証情報やクエリ文字列を含めない。
url.username = '';
url.password = '';
url.search = '';
url.hash = '';
let protocol: 'http:' | 'https:' = 'http:';
return {
url: url.toString(),
host: url.hostname,
// URL.port は既定ポートでは空文字列になるため、スキームから補う。
port: url.port === '' ? (url.protocol === 'https:' ? 443 : 80) : Number(url.port),
try {
protocol = request.protocol === 'https:' ? 'https:' : 'http:';
const host = request.getHeader('host')?.toString() ?? request.host;
const url = new URL(request.path || '/', `${protocol}//${host}`);
// URL 属性には認証情報やクエリ文字列を含めない。
url.username = '';
url.password = '';
url.search = '';
url.hash = '';
return {
url: url.toString(),
host: url.hostname,
// URL.port は既定ポートでは空文字列になるため、スキームから補う。
port: url.port === '' ? (url.protocol === 'https:' ? 443 : 80) : Number(url.port),
};
} catch {
return {
url: `${protocol}//localhost/`,
host: 'localhost',
port: protocol === 'https:' ? 443 : 80,
};
}
}
function guardDiagnosticsListener(
deps: Pick<HttpClientInstrumentationDeps, 'reportError'>,
listener: (message: unknown) => void,
): (message: unknown) => void {
return (message) => {
try {
listener(message);
} catch (error) {
try {
deps.reportError?.(error);
} catch {
// diagnostics listener の失敗をアプリケーションへ伝播させない。
}
}
};
}
@@ -4,13 +4,13 @@
*/
import { tracingChannel } from 'node:diagnostics_channel';
import { context, trace } from '@opentelemetry/api';
import { context, diag, trace } from '@opentelemetry/api';
import type { Span, SpanKind, SpanStatusCode, Tracer } from '@opentelemetry/api';
type IORedisCommandContext = {
command: string;
args: string[];
database: number;
database: number | undefined;
serverAddress: string;
serverPort: number | undefined;
};
@@ -39,6 +39,7 @@ type RedisInstrumentationDeps = {
getActiveSpan(): Span | undefined;
spanKindClient: SpanKind;
spanStatusCodeError: SpanStatusCode;
reportError?: (error: unknown) => void;
};
type RedisInstrumentationOptions = {
@@ -65,6 +66,7 @@ export function installRedisInstrumentation(
getActiveSpan: () => trace.getSpan(context.active()),
spanKindClient,
spanStatusCodeError,
reportError: error => diag.error('Failed to process Redis diagnostics event.', error),
}, options);
}
@@ -77,9 +79,9 @@ export function createRedisInstrumentation(deps: RedisInstrumentationDeps, optio
name: message.command,
attributes: {
'db.system.name': 'redis',
'db.namespace': message.database.toString(10),
'db.operation.name': message.command,
'server.address': message.serverAddress,
...(message.database != null ? { 'db.namespace': message.database.toString(10) } : {}),
...(message.serverPort != null ? { 'server.port': message.serverPort } : {}),
},
}));
@@ -138,7 +140,7 @@ function createTracingChannelSubscribers<T extends object>(
};
const subscribers: TracingChannelSubscribers<T> = {
start: (message) => {
start: guardTracingSubscriber(deps, (message: T) => {
if (requireParentSpan && deps.getActiveSpan() == null) return;
const options = getSpanOptions(message);
@@ -147,24 +149,41 @@ function createTracingChannelSubscribers<T extends object>(
attributes: options.attributes,
});
spans.set(message, { span, recordedError: false });
},
}),
// Promiseを返すコマンドでは完了前にもendイベントが来るため、同期例外だけここで閉じる。
end: (message) => {
end: guardTracingSubscriber(deps, (message: T & { error?: unknown }) => {
if (message.error != null) finish(message);
},
}),
asyncStart: () => {},
asyncEnd: finish,
error: (message) => {
asyncEnd: guardTracingSubscriber(deps, finish),
error: guardTracingSubscriber(deps, (message: T & { error: unknown }) => {
const state = spans.get(message);
if (state == null) return;
recordError(state, message.error);
},
}),
};
channel.subscribe(subscribers);
return subscribers;
}
function guardTracingSubscriber<T>(
deps: Pick<RedisInstrumentationDeps, 'reportError'>,
subscriber: (message: T) => void,
): (message: T) => void {
return (message) => {
try {
subscriber(message);
} catch (error) {
try {
deps.reportError?.(error);
} catch {
// diagnostics subscriber の失敗をアプリケーションへ伝播させない。
}
}
};
}
function toError(value: unknown): Error {
return value instanceof Error ? value : new Error(String(value));
}
@@ -73,7 +73,10 @@ export function startSpanWithTraceContext<T>(name: string, jobData: object, fn:
// Queue context を解釈できる adapter に span 作成を任せる。
// 対応 adapter が無い構成では通常の startSpan へフォールバックする。
const adapter = adapters.find(adapter => adapter.startSpanWithTraceContext != null);
return adapter?.startSpanWithTraceContext?.(name, jobData, fn) ?? startSpan(name, fn);
if (adapter?.startSpanWithTraceContext != null) {
return adapter.startSpanWithTraceContext(name, jobData, fn);
}
return startSpan(name, fn);
}
export async function shutdownTelemetry(): Promise<void> {
@@ -55,6 +55,7 @@ describe('Redis telemetry instrumentation', () => {
}, { connection, prefix });
worker.once('completed', () => resolve());
worker.once('failed', (_job, error) => reject(error));
worker.on('error', reject);
});
await tracer.startActiveSpan('HTTP POST /telemetry-test', async httpSpan => {
@@ -78,7 +79,7 @@ describe('Redis telemetry instrumentation', () => {
expect(Object.values(span.attributes)).not.toContain(secret);
}
} finally {
await worker?.close();
await worker?.close(true);
await queue.obliterate({ force: true });
await queue.close();
uninstall();
@@ -4,9 +4,11 @@
*/
import { describe, expect, test, vi } from 'vitest';
import { normalizeSentryBackendConfig } from '@/config.js';
import { SentryTelemetryAdapter, buildSentryIntegrations, buildSentryNodeOptions, buildSentryOtlpInitOptions } from '@/core/telemetry/adapters/SentryTelemetryAdapter.js';
type TestIntegration = Parameters<ReturnType<typeof buildSentryIntegrations>>[0][number];
type TestSpanProcessor = NonNullable<ReturnType<typeof buildSentryOtlpInitOptions>['openTelemetrySpanProcessors']>[number];
function testIntegration(name: string): TestIntegration {
return { name };
@@ -79,43 +81,59 @@ describe('SentryTelemetryAdapter', () => {
});
test('builds Sentry options that export spans to both Sentry and OTLP', () => {
const existingProcessor = { name: 'existingProcessor' };
const existingProcessor = { name: 'existingProcessor' } as unknown as TestSpanProcessor;
const otlpProcessor = { name: 'otlpProcessor' };
const result = buildSentryOtlpInitOptions({
sentryConfig: {
enableNodeProfiling: false,
disabledIntegrations: ['Redis'],
const result = buildSentryOtlpInitOptions({
sentryConfig: {
enableNodeProfiling: false,
disabledIntegrations: ['Redis'],
options: {
openTelemetrySpanProcessors: [existingProcessor as any],
openTelemetrySpanProcessors: [existingProcessor],
tracesSampleRate: 0.25,
},
},
otelConfig: { serviceVersion: '2026.1.0' },
otlpProcessor,
});
},
otelConfig: { serviceVersion: '2026.1.0' },
otlpProcessor,
});
expect(result.tracesSampleRate).toBe(0.25);
expect(result.openTelemetrySpanProcessors).toEqual([existingProcessor, otlpProcessor]);
// OTel併存時もremoteへtrace headerを漏らさないデフォルトはSentry単体時と揃える。
expect(result.tracePropagationTargets).toEqual([]);
expect((result.integrations as any)([
expect(result.integrations).toBeTypeOf('function');
if (typeof result.integrations !== 'function') throw new Error('Expected integrations to be a function');
expect(result.integrations([
testIntegration('Http'),
testIntegration('Redis'),
testIntegration('Postgres'),
]).map((integration: TestIntegration) => integration.name)).toEqual(['Http', 'Postgres']);
});
test('uses safe defaults when Sentry options are omitted', () => {
const otlpProcessor = { name: 'otlpProcessor' };
const result = buildSentryOtlpInitOptions({
sentryConfig: normalizeSentryBackendConfig({
enableNodeProfiling: false,
}),
otelConfig: { serviceVersion: '2026.1.0' },
otlpProcessor,
});
expect(result.tracePropagationTargets).toEqual([]);
expect(result.openTelemetrySpanProcessors).toEqual([otlpProcessor]);
});
test('does not disable Sentry trace propagation when explicitly enabled for OTel coexistence', () => {
const result = buildSentryOtlpInitOptions({
sentryConfig: {
enableNodeProfiling: false,
options: {},
},
otelConfig: {
serviceVersion: '2026.1.0',
propagateTraceToRemote: true,
},
otelConfig: {
serviceVersion: '2026.1.0',
propagateTraceToRemote: true,
},
otlpProcessor: { name: 'otlpProcessor' },
});
@@ -130,9 +148,9 @@ describe('SentryTelemetryAdapter', () => {
tracePropagationTargets: ['^https://internal\\.example/'],
},
},
otelConfig: { serviceVersion: '2026.1.0' },
otlpProcessor: { name: 'otlpProcessor' },
});
otelConfig: { serviceVersion: '2026.1.0' },
otlpProcessor: { name: 'otlpProcessor' },
});
expect(result.tracePropagationTargets).toEqual(['^https://internal\\.example/']);
});
@@ -57,7 +57,7 @@ describe('http-client-instrumentation', () => {
expect(span.setAttribute).toHaveBeenCalledWith('network.protocol.version', '1.1');
expect(span.end).toHaveBeenCalledTimes(1);
unsubscribe();
expect(listeners).toHaveLength(0);
expect(listeners.size).toBe(0);
});
test('records a request error and ends the span once', () => {
@@ -105,4 +105,84 @@ describe('http-client-instrumentation', () => {
expect(span.setAttribute).toHaveBeenCalledWith('error.type', '502');
expect(span.setStatus).toHaveBeenCalledWith({ code: SpanStatusCode.ERROR });
});
test('uses safe request details when the request URL cannot be constructed', () => {
const listeners = new Map<string, (message: unknown) => void>();
const span = { end: vi.fn(), recordException: vi.fn(), setAttribute: vi.fn(), setStatus: vi.fn() };
const tracer = { startSpan: vi.fn(() => span) };
createHttpClientInstrumentation({
tracer: tracer as any,
spanKindClient: SpanKind.CLIENT,
spanStatusCodeError: SpanStatusCode.ERROR,
subscribe: (name, listener) => {
listeners.set(name, listener);
return () => listeners.delete(name);
},
});
const clientRequest = {
...request(),
host: '::1',
getHeader: vi.fn(() => undefined),
};
listeners.get('http.client.request.created')!({ request: clientRequest });
expect(tracer.startSpan).toHaveBeenCalledWith('POST', {
kind: SpanKind.CLIENT,
attributes: {
'http.request.method': 'POST',
'url.full': 'https://localhost/',
'server.address': 'localhost',
'server.port': 443,
},
});
});
test('does not propagate errors from diagnostics listeners', () => {
const listeners = new Map<string, (message: unknown) => void>();
const reportError = vi.fn();
const responseError = new Error('response instrumentation failed');
const requestError = new Error('request instrumentation failed');
const startError = new Error('span creation failed');
const span = {
end: vi.fn(),
recordException: vi.fn(() => { throw requestError; }),
setAttribute: vi.fn(() => { throw responseError; }),
setStatus: vi.fn(),
};
const tracer = {
startSpan: vi.fn()
.mockReturnValueOnce(span)
.mockReturnValueOnce(span)
.mockImplementationOnce(() => { throw startError; }),
};
createHttpClientInstrumentation({
tracer: tracer as any,
spanKindClient: SpanKind.CLIENT,
spanStatusCodeError: SpanStatusCode.ERROR,
subscribe: (name, listener) => {
listeners.set(name, listener);
return () => listeners.delete(name);
},
reportError,
});
const responseRequest = request();
const errorRequest = request();
listeners.get('http.client.request.created')!({ request: responseRequest });
expect(() => listeners.get('http.client.response.finish')!({
request: responseRequest,
response: { statusCode: 200 },
})).not.toThrow();
listeners.get('http.client.request.created')!({ request: errorRequest });
expect(() => listeners.get('http.client.request.error')!({
request: errorRequest,
error: new Error('connection failed'),
})).not.toThrow();
expect(() => listeners.get('http.client.request.created')!({ request: request() })).not.toThrow();
expect(reportError).toHaveBeenCalledWith(responseError);
expect(reportError).toHaveBeenCalledWith(requestError);
expect(reportError).toHaveBeenCalledWith(startError);
});
});
@@ -7,8 +7,8 @@ import { describe, expect, test, vi } from 'vitest';
import type { Context, SpanContext } from '@opentelemetry/api';
import { getQueueSpanContext, getQueueTraceContextMode, injectActiveTraceContext, injectQueueTraceContext } from '@/core/telemetry/queue-trace-context.js';
const rootContext = {} as Context;
const extractedContext = {} as Context;
const rootContext = { kind: 'root' } as unknown as Context;
const extractedContext = { kind: 'extracted' } as unknown as Context;
const sourceSpanContext: SpanContext = {
traceId: '0123456789abcdef0123456789abcdef',
spanId: '0123456789abcdef',
@@ -94,6 +94,8 @@ describe('queue-trace-context', () => {
},
parentContext: rootContext,
});
expect(result?.parentContext).toBe(rootContext);
expect(getSpanContext).toHaveBeenCalledWith(extractedContext);
});
test('uses the extracted context as the parent when parent mode is selected', () => {
@@ -108,6 +110,7 @@ describe('queue-trace-context', () => {
options: {},
parentContext: extractedContext,
});
expect(result?.parentContext).toBe(extractedContext);
});
test('ignores malformed or missing carriers', () => {
@@ -145,4 +145,65 @@ describe('redis-instrumentation', () => {
expect(tracingChannel).not.toHaveBeenCalled();
});
test('omits the database attribute when diagnostics context has no database', () => {
let subscribers: any;
const span = { end: vi.fn(), recordException: vi.fn(), setStatus: vi.fn(), setAttribute: vi.fn() };
const tracer = { startSpan: vi.fn(() => span) };
createRedisInstrumentation({
tracingChannel: () => ({ subscribe: (value) => { subscribers = value; }, unsubscribe: vi.fn() }),
tracer: tracer as any,
getActiveSpan: () => ({}) as any,
spanKindClient: SpanKind.CLIENT,
spanStatusCodeError: SpanStatusCode.ERROR,
}, { captureCommandSpans: true });
subscribers.start({ command: 'get', args: ['key'], database: undefined, serverAddress: 'redis', serverPort: 6379 });
expect(tracer.startSpan).toHaveBeenCalledWith('get', expect.objectContaining({
attributes: expect.not.objectContaining({
'db.namespace': expect.anything(),
}),
}));
});
test('does not propagate errors from Redis tracing subscribers', () => {
const subscribers = new Map<string, any>();
const reportError = vi.fn();
const endError = new Error('span end failed');
const startError = new Error('span creation failed');
const span = {
end: vi.fn(() => { throw endError; }),
recordException: vi.fn(),
setStatus: vi.fn(),
setAttribute: vi.fn(),
};
const tracer = {
startSpan: vi.fn()
.mockReturnValueOnce(span)
.mockImplementationOnce(() => { throw startError; }),
};
createRedisInstrumentation({
tracingChannel: (name) => ({
subscribe: (value) => { subscribers.set(name, value); },
unsubscribe: vi.fn(),
}),
tracer: tracer as any,
getActiveSpan: () => ({}) as any,
spanKindClient: SpanKind.CLIENT,
spanStatusCodeError: SpanStatusCode.ERROR,
reportError,
}, {
captureCommandSpans: true,
captureConnectionSpans: true,
});
const command = { command: 'get', args: ['key'], database: 0, serverAddress: 'redis', serverPort: 6379 };
subscribers.get('ioredis:command').start(command);
expect(() => subscribers.get('ioredis:command').asyncEnd(command)).not.toThrow();
expect(() => subscribers.get('ioredis:connect').start({ serverAddress: 'redis', serverPort: 6379 })).not.toThrow();
expect(reportError).toHaveBeenCalledWith(endError);
expect(reportError).toHaveBeenCalledWith(startError);
});
});
@@ -127,6 +127,28 @@ describe('telemetry-registry', () => {
expect(adapterStartSpan).toHaveBeenCalledWith('test', fn);
});
test('startSpanWithTraceContext executes an undefined-returning callback only once', async () => {
const { initTelemetry, startSpanWithTraceContext } = await import('@/core/telemetry/telemetry-registry.js');
const otelForBackend = { endpoint: 'http://collector:4318/v1/traces' };
const adapterStartSpan = vi.fn((_name: string, fn: () => undefined) => fn());
const adapterStartSpanWithTraceContext = vi.fn((_name: string, _jobData: object, fn: () => undefined) => fn());
mocks.otelCreate.mockResolvedValue({
shutdown: vi.fn(),
captureMessage: vi.fn(),
startSpan: adapterStartSpan,
startSpanWithTraceContext: adapterStartSpanWithTraceContext,
});
await initTelemetry(config({ otelForBackend }));
const jobData = { id: 'job' };
const fn = vi.fn(() => undefined);
expect(startSpanWithTraceContext('test', jobData, fn)).toBeUndefined();
expect(adapterStartSpanWithTraceContext).toHaveBeenCalledWith('test', jobData, fn);
expect(adapterStartSpan).not.toHaveBeenCalled();
expect(fn).toHaveBeenCalledTimes(1);
});
test('startSpan wraps work through multiple registered adapters in order for future adapter combinations', async () => {
const { initTelemetry, startSpan } = await import('@/core/telemetry/telemetry-registry.js');
const calls: string[] = [];
+2 -2
View File
@@ -231,8 +231,8 @@ importers:
specifier: 0.220.0
version: 0.220.0(@opentelemetry/api@1.9.1)
'@opentelemetry/instrumentation':
specifier: 0.219.0
version: 0.219.0(@opentelemetry/api@1.9.1)
specifier: 0.220.0
version: 0.220.0(@opentelemetry/api@1.9.1)
'@opentelemetry/instrumentation-pg':
specifier: 0.72.0
version: 0.72.0(@opentelemetry/api@1.9.1)
-2
View File
@@ -54,8 +54,6 @@ minimumReleaseAgeExclude:
- slacc-win32-x64-msvc
- '@typescript/native-preview*'
- vite # そのうち消す
# Renovate security update: @opentelemetry/core@2.8.0
- "@opentelemetry/core@2.8.0"
# Renovate security update: typeorm@1.1.0
- typeorm@1.1.0
# Renovate security update: @fastify/static@10.1.2