@adaptic/utils 0.0.957 → 0.0.959

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -62,6 +62,7 @@ const defaultLogger = {
62
62
  debug: (msg, ctx) => console.debug(msg, normalizeContext(ctx) || ""),
63
63
  };
64
64
  let currentLogger = defaultLogger;
65
+ let loggerInjected = false;
65
66
  /**
66
67
  * Sets a custom logger implementation.
67
68
  * Call this to integrate with Pino or other logging libraries.
@@ -85,6 +86,21 @@ let currentLogger = defaultLogger;
85
86
  */
86
87
  function setLogger(logger) {
87
88
  currentLogger = logger;
89
+ loggerInjected = true;
90
+ }
91
+ /**
92
+ * Returns true when an external logger has been injected via {@link setLogger}.
93
+ *
94
+ * Used by the legacy {@link log} export in `logging.ts` to decide whether to
95
+ * route messages through the injected (production) logger or fall back to the
96
+ * `DisplayManager` terminal widget (standalone CLI usage). Without this flag,
97
+ * library code that imports `log` from `./logging` would silently bypass any
98
+ * structured logging pipeline (Pino, OTel) the host application has wired in.
99
+ *
100
+ * @returns `true` if {@link setLogger} has been called since the last reset
101
+ */
102
+ function isLoggerInjected() {
103
+ return loggerInjected;
88
104
  }
89
105
  /**
90
106
  * Gets the current logger instance.
@@ -112,6 +128,7 @@ function getLogger() {
112
128
  */
113
129
  function resetLogger() {
114
130
  currentLogger = defaultLogger;
131
+ loggerInjected = false;
115
132
  }
116
133
 
117
134
  /**
@@ -931,15 +948,91 @@ class DisplayManager {
931
948
  }
932
949
 
933
950
  /**
934
- * Logs a message to the console.
951
+ * LogType values that should map to a `Logger.info()` call when routed
952
+ * through an injected structured logger. Anything not in this set is
953
+ * treated as `info` (the safe default for unknown levels).
954
+ */
955
+ const INFO_LEVEL_TYPES = new Set([
956
+ "info",
957
+ "major",
958
+ "table",
959
+ "system",
960
+ "cost",
961
+ ]);
962
+ /**
963
+ * Builds the structured-logger context object for an injected logger call.
964
+ *
965
+ * The injected logger is expected to be a Pino-style structured logger
966
+ * (the engine wires it via `setUtilsLogger({...})` from
967
+ * `service-initializer.ts`). We surface every meaningful field from
968
+ * `LogOptions` so downstream consumers can filter, query, and correlate.
969
+ */
970
+ function buildLoggerContext(options) {
971
+ const context = {};
972
+ if (options.source)
973
+ context.source = options.source;
974
+ if (options.account)
975
+ context.account = options.account;
976
+ if (options.symbol)
977
+ context.symbol = options.symbol;
978
+ if (options.type)
979
+ context.logType = options.type;
980
+ if (options.metadata)
981
+ Object.assign(context, options.metadata);
982
+ return context;
983
+ }
984
+ /**
985
+ * Logs a message.
986
+ *
987
+ * **Routing strategy:**
988
+ * 1. If a structured logger has been injected via `setLogger()` (the engine
989
+ * does this on startup with a Pino child logger), route the message
990
+ * through that logger so it joins the centralised logging pipeline with
991
+ * full structure, source attribution, correlation IDs, and downstream
992
+ * aggregation.
993
+ * 2. Otherwise (standalone CLI usage of `@adaptic/utils`), fall back to the
994
+ * legacy `DisplayManager`, which writes directly to `process.stdout` with
995
+ * ANSI colours, prompt preservation, and optional symbol-specific log
996
+ * files. This preserves backward compatibility for scripts.
997
+ *
998
+ * Without this routing, every Alpaca client call (positions, orders,
999
+ * streams, market-data, crypto, options — 28 modules) would silently
1000
+ * bypass the host application's structured logger and emit unstructured
1001
+ * lines straight to stdout, producing a long-running observability gap
1002
+ * in production deployments.
1003
+ *
935
1004
  * @param message The message to log.
936
1005
  * @param options Optional options.
937
1006
  * @param options.source The source of the message.
938
1007
  * @param options.type The type of message to log.
939
1008
  * @param options.symbol The trading symbol associated with this log.
940
1009
  * @param options.logToFile Force logging to a file even when no symbol is provided.
1010
+ * @param options.account Optional account identifier surfaced in logs.
1011
+ * @param options.metadata Additional structured fields to merge into context.
941
1012
  */
942
1013
  function log$m(message, options = { source: "Server", type: "info" }) {
1014
+ if (isLoggerInjected()) {
1015
+ const logger = getLogger();
1016
+ const context = buildLoggerContext(options);
1017
+ const type = options.type ?? "info";
1018
+ if (type === "error") {
1019
+ logger.error(message, context);
1020
+ }
1021
+ else if (type === "warn") {
1022
+ logger.warn(message, context);
1023
+ }
1024
+ else if (type === "debug") {
1025
+ logger.debug(message, context);
1026
+ }
1027
+ else if (INFO_LEVEL_TYPES.has(type)) {
1028
+ logger.info(message, context);
1029
+ }
1030
+ else {
1031
+ logger.info(message, context);
1032
+ }
1033
+ return;
1034
+ }
1035
+ // Fallback: standalone CLI / no host logger wired in.
943
1036
  const displayManager = DisplayManager.getInstance();
944
1037
  displayManager.log(message, options);
945
1038
  }