@adaptic/utils 0.0.957 → 0.0.958

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