diff --git a/packages/effect/src/logger.ts b/packages/effect/src/logger.ts index 654edb639662..f0a55b3bf20e 100644 --- a/packages/effect/src/logger.ts +++ b/packages/effect/src/logger.ts @@ -1,7 +1,11 @@ -import { isObjectLike, logger as sentryLogger } from '@sentry/core'; +import { isObjectLike, logger as sentryLogger, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core'; import * as Logger from 'effect/Logger'; import type * as LogLevel from 'effect/LogLevel'; +const LOG_ATTRIBUTES = { + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.log.effect', +}; + function getLogLevelTag(logLevel: LogLevel.LogLevel): LogLevel.LogLevel | 'Warning' { // Effect v4: logLevel is a string literal directly if (typeof logLevel === 'string') { @@ -34,23 +38,23 @@ export const SentryEffectLogger = Logger.make(({ logLevel, message }) => { switch (tag) { case 'Fatal': - sentryLogger.fatal(msg); + sentryLogger.fatal(msg, LOG_ATTRIBUTES); break; case 'Error': - sentryLogger.error(msg); + sentryLogger.error(msg, LOG_ATTRIBUTES); break; case 'Warning': // Effect v3 case 'Warn': // Effect v4 - sentryLogger.warn(msg); + sentryLogger.warn(msg, LOG_ATTRIBUTES); break; case 'Info': - sentryLogger.info(msg); + sentryLogger.info(msg, LOG_ATTRIBUTES); break; case 'Debug': - sentryLogger.debug(msg); + sentryLogger.debug(msg, LOG_ATTRIBUTES); break; case 'Trace': - sentryLogger.trace(msg); + sentryLogger.trace(msg, LOG_ATTRIBUTES); break; case 'All': case 'None': diff --git a/packages/effect/test/logger.test.ts b/packages/effect/test/logger.test.ts index 5069514fc2c7..9b17311d195f 100644 --- a/packages/effect/test/logger.test.ts +++ b/packages/effect/test/logger.test.ts @@ -21,6 +21,8 @@ vi.mock('@sentry/core', async importOriginal => { }; }); +const LOG_ATTRIBUTES = { 'sentry.origin': 'auto.log.effect' }; + describe('SentryEffectLogger', () => { afterEach(() => { vi.clearAllMocks(); @@ -34,49 +36,49 @@ describe('SentryEffectLogger', () => { it.effect('forwards fatal logs to Sentry', () => Effect.gen(function* () { yield* Effect.logFatal('This is a fatal message'); - expect(sentryCore.logger.fatal).toHaveBeenCalledWith('This is a fatal message'); + expect(sentryCore.logger.fatal).toHaveBeenCalledWith('This is a fatal message', LOG_ATTRIBUTES); }).pipe(Effect.provide(loggerLayer)), ); it.effect('forwards error logs to Sentry', () => Effect.gen(function* () { yield* Effect.logError('This is an error message'); - expect(sentryCore.logger.error).toHaveBeenCalledWith('This is an error message'); + expect(sentryCore.logger.error).toHaveBeenCalledWith('This is an error message', LOG_ATTRIBUTES); }).pipe(Effect.provide(loggerLayer)), ); it.effect('forwards warning logs to Sentry', () => Effect.gen(function* () { yield* Effect.logWarning('This is a warning message'); - expect(sentryCore.logger.warn).toHaveBeenCalledWith('This is a warning message'); + expect(sentryCore.logger.warn).toHaveBeenCalledWith('This is a warning message', LOG_ATTRIBUTES); }).pipe(Effect.provide(loggerLayer)), ); it.effect('forwards info logs to Sentry', () => Effect.gen(function* () { yield* Effect.logInfo('This is an info message'); - expect(sentryCore.logger.info).toHaveBeenCalledWith('This is an info message'); + expect(sentryCore.logger.info).toHaveBeenCalledWith('This is an info message', LOG_ATTRIBUTES); }).pipe(Effect.provide(loggerLayer)), ); it.effect('forwards debug logs to Sentry', () => Effect.gen(function* () { yield* Effect.logDebug('This is a debug message'); - expect(sentryCore.logger.debug).toHaveBeenCalledWith('This is a debug message'); + expect(sentryCore.logger.debug).toHaveBeenCalledWith('This is a debug message', LOG_ATTRIBUTES); }).pipe(withAllLogLevels, Effect.provide(loggerLayer)), ); it.effect('forwards trace logs to Sentry', () => Effect.gen(function* () { yield* Effect.logTrace('This is a trace message'); - expect(sentryCore.logger.trace).toHaveBeenCalledWith('This is a trace message'); + expect(sentryCore.logger.trace).toHaveBeenCalledWith('This is a trace message', LOG_ATTRIBUTES); }).pipe(withAllLogLevels, Effect.provide(loggerLayer)), ); it.effect('handles object messages by stringifying', () => Effect.gen(function* () { yield* Effect.logInfo({ key: 'value', nested: { foo: 'bar' } }); - expect(sentryCore.logger.info).toHaveBeenCalledWith('{"key":"value","nested":{"foo":"bar"}}'); + expect(sentryCore.logger.info).toHaveBeenCalledWith('{"key":"value","nested":{"foo":"bar"}}', LOG_ATTRIBUTES); }).pipe(Effect.provide(loggerLayer)), ); @@ -86,9 +88,9 @@ describe('SentryEffectLogger', () => { yield* Effect.logInfo('Second message'); yield* Effect.logWarning('Third message'); expect(sentryCore.logger.info).toHaveBeenCalledTimes(2); - expect(sentryCore.logger.info).toHaveBeenNthCalledWith(1, 'First message'); - expect(sentryCore.logger.info).toHaveBeenNthCalledWith(2, 'Second message'); - expect(sentryCore.logger.warn).toHaveBeenCalledWith('Third message'); + expect(sentryCore.logger.info).toHaveBeenNthCalledWith(1, 'First message', LOG_ATTRIBUTES); + expect(sentryCore.logger.info).toHaveBeenNthCalledWith(2, 'Second message', LOG_ATTRIBUTES); + expect(sentryCore.logger.warn).toHaveBeenCalledWith('Third message', LOG_ATTRIBUTES); }).pipe(Effect.provide(loggerLayer)), ); @@ -99,7 +101,25 @@ describe('SentryEffectLogger', () => { Effect.map(d => d.toUpperCase()), ); expect(result).toBe('DATA'); - expect(sentryCore.logger.info).toHaveBeenCalledWith('Processing: data'); + expect(sentryCore.logger.info).toHaveBeenCalledWith('Processing: data', LOG_ATTRIBUTES); }).pipe(Effect.provide(loggerLayer)), ); + + it.effect('sets the sentry.origin attribute on every log level', () => + Effect.gen(function* () { + yield* Effect.logFatal('fatal'); + yield* Effect.logError('error'); + yield* Effect.logWarning('warning'); + yield* Effect.logInfo('info'); + yield* Effect.logDebug('debug'); + yield* Effect.logTrace('trace'); + + for (const level of ['fatal', 'error', 'warn', 'info', 'debug', 'trace'] as const) { + expect(sentryCore.logger[level]).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ 'sentry.origin': 'auto.log.effect' }), + ); + } + }).pipe(withAllLogLevels, Effect.provide(loggerLayer)), + ); });