From 081eb1047bb62baa93c0a699e2d1a92b5a11af2d Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Mon, 8 Dec 2025 13:30:59 +0100 Subject: [PATCH] feat: include callee information --- .../browser/src/background/main.background.ts | 1 + .../service-container/service-container.ts | 1 + .../src/services/jslib-services.module.ts | 1 + .../services/sdk/default-sdk.service.spec.ts | 4 + .../services/sdk/default-sdk.service.ts | 93 ++++++++++++------- libs/logging/src/console-log.service.ts | 67 +++++++++++++ libs/logging/src/log.service.ts | 10 ++ package-lock.json | 55 +++++++++++ package.json | 3 +- 9 files changed, 202 insertions(+), 33 deletions(-) diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 1bd47186914..45e4d3dfda7 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -838,6 +838,7 @@ export default class MainBackground { this.apiService, this.stateProvider, this.configService, + this.logService, ); this.pinService = new PinService( diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index 83c64c61423..b1aeb66f433 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -668,6 +668,7 @@ export class ServiceContainer { this.apiService, this.stateProvider, this.configService, + this.logService, customUserAgent, ); diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index c2d4440b449..30c0ecc3507 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -1641,6 +1641,7 @@ const safeProviders: SafeProvider[] = [ ApiServiceAbstraction, StateProvider, ConfigService, + LogService, ], }), safeProvider({ diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts b/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts index 1286ea7b7f9..38d1db57973 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts @@ -20,6 +20,7 @@ import { UserId } from "../../../types/guid"; import { UserKey } from "../../../types/key"; import { ConfigService } from "../../abstractions/config/config.service"; import { Environment, EnvironmentService } from "../../abstractions/environment.service"; +import { LogService } from "../../abstractions/log.service"; import { PlatformUtilsService } from "../../abstractions/platform-utils.service"; import { SdkClientFactory } from "../../abstractions/sdk/sdk-client-factory"; import { SdkLoadService } from "../../abstractions/sdk/sdk-load.service"; @@ -49,6 +50,7 @@ describe("DefaultSdkService", () => { let service!: DefaultSdkService; let accountService!: FakeAccountService; let fakeStateProvider!: FakeStateProvider; + let logService!: MockProxy; let apiService!: MockProxy; beforeEach(async () => { @@ -64,6 +66,7 @@ describe("DefaultSdkService", () => { const mockUserId = Utils.newGuid() as UserId; accountService = mockAccountServiceWith(mockUserId); fakeStateProvider = new FakeStateProvider(accountService); + logService = mock(); configService = mock(); configService.serverConfig$ = new BehaviorSubject(null); @@ -82,6 +85,7 @@ describe("DefaultSdkService", () => { apiService, fakeStateProvider, configService, + logService, ); }); diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.ts b/libs/common/src/platform/services/sdk/default-sdk.service.ts index 9d0aeb46890..7bf861627f3 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.ts @@ -23,11 +23,8 @@ import { KeyService, KdfConfigService, KdfConfig, KdfType } from "@bitwarden/key import { PasswordManagerClient, ClientSettings, - EventDefinition, FieldValue, - DeviceType as SdkDeviceType, Span, - SpanDefinition, TokenProvider, TracingLevel, UnsignedSharedKey, @@ -40,6 +37,7 @@ import { EncString } from "../../../key-management/crypto/models/enc-string"; import { SecurityStateService } from "../../../key-management/security-state/abstractions/security-state.service"; import { OrganizationId, UserId } from "../../../types/guid"; import { Environment, EnvironmentService } from "../../abstractions/environment.service"; +import { LogService } from "../../abstractions/log.service"; import { PlatformUtilsService } from "../../abstractions/platform-utils.service"; import { SdkClientFactory } from "../../abstractions/sdk/sdk-client-factory"; import { SdkLoadService } from "../../abstractions/sdk/sdk-load.service"; @@ -55,34 +53,34 @@ import { StateProvider } from "../../state"; import { initializeState } from "./client-managed-state"; -const UserClientSpan = SdkLoadService.WithSdk( - () => - new SpanDefinition("userClient$", "DefaultSdkService", TracingLevel.Info, [ - "userId", - "hasOverride", - ]), -); +// const UserClientSpan = SdkLoadService.WithSdk( +// () => +// new SpanDefinition("userClient$", "DefaultSdkService", TracingLevel.Info, [ +// "userId", +// "hasOverride", +// ]), +// ); -const InitializeClientSpan = SdkLoadService.WithSdk( - // TODO: We can remove userId because it's already in the parent span - () => new SpanDefinition("initializeClient", "DefaultSdkService", TracingLevel.Info, ["userId"]), -); +// const InitializeClientSpan = SdkLoadService.WithSdk( +// // TODO: We can remove userId because it's already in the parent span +// () => new SpanDefinition("initializeClient", "DefaultSdkService", TracingLevel.Info, ["userId"]), +// ); -const UserCryptoInitializedEvent = SdkLoadService.WithSdk( - () => new EventDefinition("User crypto initialized", "DefaultSdkService", TracingLevel.Info, []), -); +// const UserCryptoInitializedEvent = SdkLoadService.WithSdk( +// () => new EventDefinition("User crypto initialized", "DefaultSdkService", TracingLevel.Info, []), +// ); -const OrgCryptoInitializedEvent = SdkLoadService.WithSdk( - () => new EventDefinition("Org crypto initialized", "DefaultSdkService", TracingLevel.Info, []), -); +// const OrgCryptoInitializedEvent = SdkLoadService.WithSdk( +// () => new EventDefinition("Org crypto initialized", "DefaultSdkService", TracingLevel.Info, []), +// ); -const ClientStateInitializedEvent = SdkLoadService.WithSdk( - () => new EventDefinition("Client state initialized", "DefaultSdkService", TracingLevel.Info, []), -); +// const ClientStateInitializedEvent = SdkLoadService.WithSdk( +// () => new EventDefinition("Client state initialized", "DefaultSdkService", TracingLevel.Info, []), +// ); -const FeatureFlagsLoadedEvent = SdkLoadService.WithSdk( - () => new EventDefinition("Feature flags loaded", "DefaultSdkService", TracingLevel.Info, []), -); +// const FeatureFlagsLoadedEvent = SdkLoadService.WithSdk( +// () => new EventDefinition("Feature flags loaded", "DefaultSdkService", TracingLevel.Info, []), +// ); // A symbol that represents an overridden client that is explicitly set to undefined, // blocking the creation of an internal client for that user. @@ -142,11 +140,14 @@ export class DefaultSdkService implements SdkService { private apiService: ApiService, private stateProvider: StateProvider, private configService: ConfigService, + private logService: LogService, private userAgent: string | null = null, ) {} userClient$(userId: UserId): Observable> { - const span = UserClientSpan.requiredValue.enter([new FieldValue("userId", userId)]); + const span = this.logService.span("userClient$", TracingLevel.Info, [ + new FieldValue("userId", userId), + ]); return this.sdkClientOverrides.pipe( takeWhile((clients) => clients[userId] !== UnsetClient, false), map((clients) => { @@ -320,7 +321,10 @@ export class DefaultSdkService implements SdkService { orgKeys: Record, parent: Span, ) { - using span = (await InitializeClientSpan).enter_with_parent(parent, [ + // using span = (await InitializeClientSpan).enter_with_parent(parent, [ + // new FieldValue("userId", userId), + // ]); + using span = this.logService.span("initializeClient", TracingLevel.Info, [ new FieldValue("userId", userId), ]); @@ -340,7 +344,14 @@ export class DefaultSdkService implements SdkService { }, accountCryptographicState: accountCryptographicState, }); - span.event(await UserCryptoInitializedEvent, `User crypto initialized for user`); + // span.event(await UserCryptoInitializedEvent, `User crypto initialized for user`); + this.logService.event( + span, + "cryptoInitialized", + `User crypto initialized for user`, + TracingLevel.Info, + [], + ); // We initialize the org crypto even if the org_keys are // null to make sure any existing org keys are cleared. @@ -349,14 +360,32 @@ export class DefaultSdkService implements SdkService { Object.entries(orgKeys).map(([k, v]) => [asUuid(k), v.toJSON() as UnsignedSharedKey]), ), }); - span.event(await OrgCryptoInitializedEvent, `Org crypto initialized for user`); + this.logService.event( + span, + "orgCryptoInitialized", + `Org crypto initialized for user`, + TracingLevel.Info, + [], + ); // Initialize the SDK managed database and the client managed repositories. await initializeState(userId, client.platform().state(), this.stateProvider); - span.event(await ClientStateInitializedEvent, "Client state initialized"); + this.logService.event( + span, + "clientStateInitialized", + `Client state initialized`, + TracingLevel.Info, + [], + ); await this.loadFeatureFlags(client); - span.event(await FeatureFlagsLoadedEvent, "Feature flags loaded"); + this.logService.event( + span, + "featureFlagsLoaded", + `Feature flags loaded`, + TracingLevel.Info, + [], + ); } private async loadFeatureFlags(client: PasswordManagerClient) { diff --git a/libs/logging/src/console-log.service.ts b/libs/logging/src/console-log.service.ts index 29246368cf7..df8d818f233 100644 --- a/libs/logging/src/console-log.service.ts +++ b/libs/logging/src/console-log.service.ts @@ -1,14 +1,81 @@ +// import { parse } from "stacktrace-parser"; +import StackTrace from "stacktrace-js"; + +import { + EventDefinition, + FieldValue, + Span, + SpanDefinition, + TracingLevel, +} from "@bitwarden/sdk-internal"; + import { LogLevel } from "./log-level"; import { LogService } from "./log.service"; export class ConsoleLogService implements LogService { protected timersMap: Map = new Map(); + protected spanDefinitions = new Map(); + protected eventDefinitions = new Map(); constructor( protected isDev: boolean, protected filter: ((level: LogLevel) => boolean) | null = null, ) {} + /** + * Creates a new span. + * @param name Name of the span to create. Must be unique in the application. + * // TODO: Consider creating the name as we do state KeyDefinitions. + */ + span(name: string, level: TracingLevel, fields: FieldValue[]): Span { + let definition = this.spanDefinitions.get(name); + if (!definition) { + // TODO: A better way to get the caller info would be to use Webpack to modify + // the code calling logService.span(...) at compile time to inject the file, line, and function name. + const stack = StackTrace.getSync(); + const callee = stack[1]; + definition = new SpanDefinition( + name, + "", + level, + fields.map((f) => f.name), + callee.fileName, + callee.lineNumber, + callee.functionName, + ); + this.spanDefinitions.set(name, definition); + } + return definition.enter(fields); + } + + event( + span: Span, + name: string, + message: string, + level: TracingLevel, + fields: FieldValue[], + ): void { + let definition = this.eventDefinitions.get(name); + if (!definition) { + // TODO: A better way to get the caller info would be to use Webpack to modify + // the code calling logService.event(...) at compile time to inject the file, line, and function name. + const stack = StackTrace.getSync(); + const callee = stack[1]; + + definition = new EventDefinition( + name, + callee.functionName ?? "", + level, + fields.map((f) => f.name), + callee.fileName, + callee.lineNumber, + callee.functionName, + ); + this.eventDefinitions.set(name, definition); + } + span.event(definition, message); + } + debug(message?: any, ...optionalParams: any[]) { if (!this.isDev) { return; diff --git a/libs/logging/src/log.service.ts b/libs/logging/src/log.service.ts index ce391723bf9..1c915df5fd2 100644 --- a/libs/logging/src/log.service.ts +++ b/libs/logging/src/log.service.ts @@ -1,6 +1,16 @@ +import type { FieldValue, Span, TracingLevel } from "@bitwarden/sdk-internal"; + import { LogLevel } from "./log-level"; export abstract class LogService { + abstract span(name: string, level: TracingLevel, fields: FieldValue[]): Span; + abstract event( + span: Span, + name: string, + message: string, + level: TracingLevel, + fields: FieldValue[], + ): void; abstract debug(message?: any, ...optionalParams: any[]): void; abstract info(message?: any, ...optionalParams: any[]): void; abstract warning(message?: any, ...optionalParams: any[]): void; diff --git a/package-lock.json b/package-lock.json index b03c0c7b20c..ceb2570a71b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -67,6 +67,7 @@ "qrious": "4.0.2", "rxjs": "7.8.1", "semver": "7.7.3", + "stacktrace-js": "2.0.2", "tabbable": "6.3.0", "tldts": "7.0.19", "ts-node": "10.9.2", @@ -20910,6 +20911,15 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "license": "MIT", + "dependencies": { + "stackframe": "^1.3.4" + } + }, "node_modules/es-abstract": { "version": "1.24.0", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", @@ -37379,6 +37389,15 @@ "dev": true, "license": "MIT" }, + "node_modules/stack-generator": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.10.tgz", + "integrity": "sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==", + "license": "MIT", + "dependencies": { + "stackframe": "^1.3.4" + } + }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -37400,6 +37419,42 @@ "node": ">=8" } }, + "node_modules/stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", + "license": "MIT" + }, + "node_modules/stacktrace-gps": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/stacktrace-gps/-/stacktrace-gps-3.1.2.tgz", + "integrity": "sha512-GcUgbO4Jsqqg6RxfyTHFiPxdPqF+3LFmQhm7MgCuYQOYuWyqxo5pwRPz5d/u6/WYJdEnWfK4r+jGbyD8TSggXQ==", + "license": "MIT", + "dependencies": { + "source-map": "0.5.6", + "stackframe": "^1.3.4" + } + }, + "node_modules/stacktrace-gps/node_modules/source-map": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "integrity": "sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stacktrace-js": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stacktrace-js/-/stacktrace-js-2.0.2.tgz", + "integrity": "sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==", + "license": "MIT", + "dependencies": { + "error-stack-parser": "^2.0.6", + "stack-generator": "^2.0.5", + "stacktrace-gps": "^3.0.4" + } + }, "node_modules/stat-mode": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz", diff --git a/package.json b/package.json index fd56093210f..6e4811ab249 100644 --- a/package.json +++ b/package.json @@ -157,8 +157,8 @@ "@angular/platform-browser": "20.3.15", "@angular/platform-browser-dynamic": "20.3.15", "@angular/router": "20.3.15", - "@bitwarden/sdk-internal": "0.2.0-main.409", "@bitwarden/commercial-sdk-internal": "0.2.0-main.409", + "@bitwarden/sdk-internal": "0.2.0-main.409", "@electron/fuses": "1.8.0", "@emotion/css": "11.13.5", "@koa/multer": "4.0.0", @@ -201,6 +201,7 @@ "qrious": "4.0.2", "rxjs": "7.8.1", "semver": "7.7.3", + "stacktrace-js": "2.0.2", "tabbable": "6.3.0", "tldts": "7.0.19", "ts-node": "10.9.2",