jspurefix 5.11.1 → 5.11.2

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.
Files changed (117) hide show
  1. package/README.md +13 -1
  2. package/dist/buffer/fixml/fixml-parser.js +1 -1
  3. package/dist/buffer/fixml/fixml-parser.js.map +1 -1
  4. package/dist/config/empty-log-factory.d.ts +2 -2
  5. package/dist/config/empty-log-factory.js +1 -1
  6. package/dist/config/empty-log-factory.js.map +1 -1
  7. package/dist/config/get-js-fx-logger.d.ts +2 -2
  8. package/dist/config/get-js-fx-logger.js.map +1 -1
  9. package/dist/config/js-fix-logger-factory.d.ts +2 -2
  10. package/dist/config/js-fix-logger-factory.js.map +1 -1
  11. package/dist/config/js-fix-logger.d.ts +9 -8
  12. package/dist/config/js-fix-logger.js +4 -4
  13. package/dist/config/js-fix-logger.js.map +1 -1
  14. package/dist/config/js-fix-winston-log-factory.d.ts +2 -2
  15. package/dist/config/js-fix-winston-log-factory.js +2 -2
  16. package/dist/config/js-fix-winston-log-factory.js.map +1 -1
  17. package/dist/config/winston-logger.d.ts +5 -2
  18. package/dist/config/winston-logger.js +55 -9
  19. package/dist/config/winston-logger.js.map +1 -1
  20. package/dist/dictionary/fragment-safety.d.ts +26 -0
  21. package/dist/dictionary/fragment-safety.js +154 -0
  22. package/dist/dictionary/fragment-safety.js.map +1 -0
  23. package/dist/dictionary/index.d.ts +1 -0
  24. package/dist/dictionary/index.js +1 -0
  25. package/dist/dictionary/index.js.map +1 -1
  26. package/dist/runtime/make-config.js +1 -1
  27. package/dist/runtime/make-config.js.map +1 -1
  28. package/dist/runtime/session-launcher.js +1 -1
  29. package/dist/runtime/session-launcher.js.map +1 -1
  30. package/dist/sample/http/oms/http-client.js +1 -1
  31. package/dist/sample/http/oms/http-client.js.map +1 -1
  32. package/dist/sample/http/oms/http-server.js +1 -1
  33. package/dist/sample/http/oms/http-server.js.map +1 -1
  34. package/dist/sample/tcp/qf-md/md-client.js +1 -1
  35. package/dist/sample/tcp/qf-md/md-client.js.map +1 -1
  36. package/dist/sample/tcp/qf-md/md-server.js +1 -1
  37. package/dist/sample/tcp/qf-md/md-server.js.map +1 -1
  38. package/dist/sample/tcp/recovering-skeleton/respawn-acceptor.js +1 -1
  39. package/dist/sample/tcp/recovering-skeleton/respawn-acceptor.js.map +1 -1
  40. package/dist/sample/tcp/recovering-skeleton/skeleton-client.js +1 -1
  41. package/dist/sample/tcp/recovering-skeleton/skeleton-client.js.map +1 -1
  42. package/dist/sample/tcp/recovering-skeleton/skeleton-server.js +1 -1
  43. package/dist/sample/tcp/recovering-skeleton/skeleton-server.js.map +1 -1
  44. package/dist/sample/tcp/skeleton/skeleton-session.js +1 -1
  45. package/dist/sample/tcp/skeleton/skeleton-session.js.map +1 -1
  46. package/dist/sample/tcp/trade-capture/trade-capture-client.js +1 -1
  47. package/dist/sample/tcp/trade-capture/trade-capture-client.js.map +1 -1
  48. package/dist/sample/tcp/trade-capture/trade-capture-server.js +1 -1
  49. package/dist/sample/tcp/trade-capture/trade-capture-server.js.map +1 -1
  50. package/dist/store/fix-msg-memory-store.js +1 -1
  51. package/dist/store/fix-msg-memory-store.js.map +1 -1
  52. package/dist/transport/ascii/ascii-msg-transmitter.js +1 -1
  53. package/dist/transport/ascii/ascii-msg-transmitter.js.map +1 -1
  54. package/dist/transport/ascii/ascii-session.js +1 -1
  55. package/dist/transport/ascii/ascii-session.js.map +1 -1
  56. package/dist/transport/http/http-acceptor-listener.js +1 -1
  57. package/dist/transport/http/http-acceptor-listener.js.map +1 -1
  58. package/dist/transport/http/http-acceptor.js +2 -2
  59. package/dist/transport/http/http-acceptor.js.map +1 -1
  60. package/dist/transport/http/http-initiator.js +1 -1
  61. package/dist/transport/http/http-initiator.js.map +1 -1
  62. package/dist/transport/http/http-json-sample-adapter.js +1 -1
  63. package/dist/transport/http/http-json-sample-adapter.js.map +1 -1
  64. package/dist/transport/session/fix-session.js +3 -3
  65. package/dist/transport/session/fix-session.js.map +1 -1
  66. package/dist/transport/session/session-registry.js +1 -1
  67. package/dist/transport/session/session-registry.js.map +1 -1
  68. package/dist/transport/tcp/recovering-tcp-initiator.js +1 -1
  69. package/dist/transport/tcp/recovering-tcp-initiator.js.map +1 -1
  70. package/dist/transport/tcp/tcp-acceptor-listener.js +2 -2
  71. package/dist/transport/tcp/tcp-acceptor-listener.js.map +1 -1
  72. package/dist/transport/tcp/tcp-acceptor.js +2 -2
  73. package/dist/transport/tcp/tcp-acceptor.js.map +1 -1
  74. package/dist/transport/tcp/tcp-initiator-connector.js +2 -2
  75. package/dist/transport/tcp/tcp-initiator-connector.js.map +1 -1
  76. package/dist/transport/tcp/tcp-initiator.js +1 -1
  77. package/dist/transport/tcp/tcp-initiator.js.map +1 -1
  78. package/docs/instrumentation.md +346 -0
  79. package/docs/scattered-components.md +280 -0
  80. package/jsfix.test_client.txt +62 -62
  81. package/jsfix.test_server.txt +72 -72
  82. package/package.json +1 -1
  83. package/src/buffer/fixml/fixml-parser.ts +1 -1
  84. package/src/config/empty-log-factory.ts +2 -2
  85. package/src/config/get-js-fx-logger.ts +2 -2
  86. package/src/config/js-fix-logger-factory.ts +8 -2
  87. package/src/config/js-fix-logger.ts +23 -8
  88. package/src/config/js-fix-winston-log-factory.ts +3 -3
  89. package/src/config/winston-logger.ts +85 -10
  90. package/src/dictionary/fragment-safety.ts +225 -0
  91. package/src/dictionary/index.ts +1 -0
  92. package/src/runtime/make-config.ts +1 -1
  93. package/src/runtime/session-launcher.ts +1 -1
  94. package/src/sample/http/oms/http-client.ts +1 -1
  95. package/src/sample/http/oms/http-server.ts +1 -1
  96. package/src/sample/tcp/qf-md/md-client.ts +1 -1
  97. package/src/sample/tcp/qf-md/md-server.ts +1 -1
  98. package/src/sample/tcp/recovering-skeleton/respawn-acceptor.ts +2 -1
  99. package/src/sample/tcp/recovering-skeleton/skeleton-client.ts +1 -1
  100. package/src/sample/tcp/recovering-skeleton/skeleton-server.ts +1 -1
  101. package/src/sample/tcp/skeleton/skeleton-session.ts +1 -1
  102. package/src/sample/tcp/trade-capture/trade-capture-client.ts +2 -1
  103. package/src/sample/tcp/trade-capture/trade-capture-server.ts +2 -1
  104. package/src/store/fix-msg-memory-store.ts +2 -1
  105. package/src/transport/ascii/ascii-msg-transmitter.ts +2 -1
  106. package/src/transport/ascii/ascii-session.ts +2 -1
  107. package/src/transport/http/http-acceptor-listener.ts +2 -1
  108. package/src/transport/http/http-acceptor.ts +2 -1
  109. package/src/transport/http/http-initiator.ts +2 -1
  110. package/src/transport/http/http-json-sample-adapter.ts +1 -1
  111. package/src/transport/session/fix-session.ts +2 -1
  112. package/src/transport/session/session-registry.ts +1 -1
  113. package/src/transport/tcp/recovering-tcp-initiator.ts +2 -1
  114. package/src/transport/tcp/tcp-acceptor-listener.ts +4 -2
  115. package/src/transport/tcp/tcp-acceptor.ts +2 -1
  116. package/src/transport/tcp/tcp-initiator-connector.ts +4 -2
  117. package/src/transport/tcp/tcp-initiator.ts +2 -1
@@ -1 +1 @@
1
- {"version":3,"file":"tcp-acceptor-listener.js","sourceRoot":"","sources":["../../../src/transport/tcp/tcp-acceptor-listener.ts"],"names":[],"mappings":"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","sourcesContent":["import { IJsFixConfig, IJsFixLogger } from '../../config'\nimport { FixAcceptor } from '../fix-acceptor'\nimport { TcpAcceptor } from './tcp-acceptor'\nimport { MsgTransport } from '../factory'\nimport { inject, injectable } from 'tsyringe'\nimport { FixSession } from '../session/fix-session'\nimport { DITokens } from '../../runtime/di-tokens'\nimport { FixEntity } from '../fix-entity'\nimport { ISessionRegistry, SessionRegistry } from '../session/session-registry'\n\n@injectable()\nexport class TcpAcceptorListener extends FixEntity {\n private acceptor: FixAcceptor | null = null\n private resolveStart: ((value: any) => void) | null = null\n /**\n * One registry per listener, so a counterparty reconnecting onto a fresh socket\n * replaces its previous session instead of running alongside it (issue #153).\n * An application may supply its own via config.sessionRegistry.\n */\n public readonly sessionRegistry: ISessionRegistry\n\n constructor (@inject(DITokens.IJsFixConfig) public readonly config: IJsFixConfig) {\n super(config)\n this.sessionRegistry = config.sessionRegistry ?? new SessionRegistry(config.logFactory)\n config.sessionRegistry = this.sessionRegistry\n }\n\n async start (): Promise<any> {\n return await new Promise<any>(async (resolve, reject) => {\n const logger = this.config.logFactory.logger('acceptor')\n const sessionContainer = this.config.sessionContainer\n if (!sessionContainer.isRegistered(DITokens.FixSession)) {\n reject(new Error(`application must register a DI token '${DITokens.FixSession}' - see src/sample`))\n return\n }\n logger.info('starting.')\n const acceptor: FixAcceptor = sessionContainer.resolve<FixAcceptor>(TcpAcceptor)\n this.acceptor = acceptor\n\n acceptor.on('transport', (t: MsgTransport) => {\n // resolve from the transport's own scope, not the listener's container, so\n // this session gets the per-connection description, buffers and message\n // factory created by TcpAcceptor. Falls back to the listener container for\n // transports created outside the acceptor (tests, custom entities).\n const scope = t.config?.sessionContainer ?? sessionContainer\n const description = t.config?.description\n logger.info(`transport ${t.id}: creating session via DI token ${DITokens.FixSession} ` +\n `[${description?.SenderCompId ?? '?'} -> ${description?.TargetCompID ?? '?'}]`)\n const acceptorSession = scope.resolve<FixSession>(DITokens.FixSession)\n this.emit('session', acceptorSession, t)\n // a wildcard acceptor has not claimed its SessionId yet - report again once\n // the session ends, by which time the registry line is meaningful\n this.census(logger)\n // Run the session but do NOT close the listener when it ends.\n // The acceptor keeps listening for new connections (reconnects).\n // This matches the C# TcpAcceptorListener accept-loop pattern.\n acceptorSession.run(t).then(() => {\n logger.info(`transport ${t.id}: session ended normally, acceptor continues listening`)\n this.census(logger)\n }).catch((e: Error) => {\n logger.warning(`transport ${t.id}: session ended with error: ${e.message}, acceptor continues listening`)\n this.census(logger)\n })\n })\n\n acceptor.on('harvested', (tid: number, reason: string) => {\n logger.info(`transport ${tid} harvested (${reason})`)\n this.census(logger)\n })\n\n this.resolveStart = resolve\n acceptor.listen()\n })\n }\n\n /**\n * What is actually connected, and which SessionIds are claimed. Emitted whenever\n * the population changes so \"how many clients do I have, and who are they\" is\n * answerable from the server log alone.\n */\n private census (logger: IJsFixLogger): void {\n const acceptor = this.acceptor as TcpAcceptor | null\n const transports = acceptor?.liveTransports() ?? []\n logger.info(`acceptor census: transports=${transports.length} [${transports.join(', ')}] ` +\n `sessions=${this.sessionRegistry.count} [${this.sessionRegistry.keys().join(', ')}]`)\n }\n\n /**\n * Explicitly stop the acceptor listener. Call this when the application\n * is shutting down (e.g., timeout, SIGINT). Without this call, the\n * acceptor keeps listening indefinitely — which is the correct\n * production behaviour for a FIX acceptor.\n */\n stop (): void {\n if (this.acceptor) {\n const logger = this.config.logFactory.logger('acceptor')\n logger.info('acceptor stopping')\n this.acceptor.close(() => {\n logger.info('acceptor closed.')\n if (this.resolveStart) {\n this.resolveStart(true)\n this.resolveStart = null\n }\n })\n this.acceptor = null\n }\n }\n}\n"]}
1
+ {"version":3,"file":"tcp-acceptor-listener.js","sourceRoot":"","sources":["../../../src/transport/tcp/tcp-acceptor-listener.ts"],"names":[],"mappings":"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","sourcesContent":["import { IJsFixConfig, IJsFixLogger } from '../../config'\nimport { FixAcceptor } from '../fix-acceptor'\nimport { TcpAcceptor } from './tcp-acceptor'\nimport { MsgTransport } from '../factory'\nimport { inject, injectable } from 'tsyringe'\nimport { FixSession } from '../session/fix-session'\nimport { DITokens } from '../../runtime/di-tokens'\nimport { FixEntity } from '../fix-entity'\nimport { ISessionRegistry, SessionRegistry } from '../session/session-registry'\n\n@injectable()\nexport class TcpAcceptorListener extends FixEntity {\n private acceptor: FixAcceptor | null = null\n private resolveStart: ((value: any) => void) | null = null\n /**\n * One registry per listener, so a counterparty reconnecting onto a fresh socket\n * replaces its previous session instead of running alongside it (issue #153).\n * An application may supply its own via config.sessionRegistry.\n */\n public readonly sessionRegistry: ISessionRegistry\n\n constructor (@inject(DITokens.IJsFixConfig) public readonly config: IJsFixConfig) {\n super(config)\n this.sessionRegistry = config.sessionRegistry ?? new SessionRegistry(config.logFactory)\n config.sessionRegistry = this.sessionRegistry\n }\n\n async start (): Promise<any> {\n return await new Promise<any>(async (resolve, reject) => {\n const logger = this.config.logFactory.logger('acceptor',\n { component: 'TcpAcceptorListener', role: 'acceptor' })\n const sessionContainer = this.config.sessionContainer\n if (!sessionContainer.isRegistered(DITokens.FixSession)) {\n reject(new Error(`application must register a DI token '${DITokens.FixSession}' - see src/sample`))\n return\n }\n logger.info('starting.')\n const acceptor: FixAcceptor = sessionContainer.resolve<FixAcceptor>(TcpAcceptor)\n this.acceptor = acceptor\n\n acceptor.on('transport', (t: MsgTransport) => {\n // resolve from the transport's own scope, not the listener's container, so\n // this session gets the per-connection description, buffers and message\n // factory created by TcpAcceptor. Falls back to the listener container for\n // transports created outside the acceptor (tests, custom entities).\n const scope = t.config?.sessionContainer ?? sessionContainer\n const description = t.config?.description\n logger.info(`transport ${t.id}: creating session via DI token ${DITokens.FixSession} ` +\n `[${description?.SenderCompId ?? '?'} -> ${description?.TargetCompID ?? '?'}]`)\n const acceptorSession = scope.resolve<FixSession>(DITokens.FixSession)\n this.emit('session', acceptorSession, t)\n // a wildcard acceptor has not claimed its SessionId yet - report again once\n // the session ends, by which time the registry line is meaningful\n this.census(logger)\n // Run the session but do NOT close the listener when it ends.\n // The acceptor keeps listening for new connections (reconnects).\n // This matches the C# TcpAcceptorListener accept-loop pattern.\n acceptorSession.run(t).then(() => {\n logger.info(`transport ${t.id}: session ended normally, acceptor continues listening`)\n this.census(logger)\n }).catch((e: Error) => {\n logger.warning(`transport ${t.id}: session ended with error: ${e.message}, acceptor continues listening`)\n this.census(logger)\n })\n })\n\n acceptor.on('harvested', (tid: number, reason: string) => {\n logger.info(`transport ${tid} harvested (${reason})`)\n this.census(logger)\n })\n\n this.resolveStart = resolve\n acceptor.listen()\n })\n }\n\n /**\n * What is actually connected, and which SessionIds are claimed. Emitted whenever\n * the population changes so \"how many clients do I have, and who are they\" is\n * answerable from the server log alone.\n */\n private census (logger: IJsFixLogger): void {\n const acceptor = this.acceptor as TcpAcceptor | null\n const transports = acceptor?.liveTransports() ?? []\n logger.info(`acceptor census: transports=${transports.length} [${transports.join(', ')}] ` +\n `sessions=${this.sessionRegistry.count} [${this.sessionRegistry.keys().join(', ')}]`)\n }\n\n /**\n * Explicitly stop the acceptor listener. Call this when the application\n * is shutting down (e.g., timeout, SIGINT). Without this call, the\n * acceptor keeps listening indefinitely — which is the correct\n * production behaviour for a FIX acceptor.\n */\n stop (): void {\n if (this.acceptor) {\n const logger = this.config.logFactory.logger('acceptor',\n { component: 'TcpAcceptorListener', role: 'acceptor' })\n logger.info('acceptor stopping')\n this.acceptor.close(() => {\n logger.info('acceptor closed.')\n if (this.resolveStart) {\n this.resolveStart(true)\n this.resolveStart = null\n }\n })\n this.acceptor = null\n }\n }\n}\n"]}
@@ -26,12 +26,12 @@ const di_tokens_1 = require("../../runtime/di-tokens");
26
26
  const session_scope_1 = require("../../runtime/session-scope");
27
27
  let TcpAcceptor = TcpAcceptor_1 = class TcpAcceptor extends fix_acceptor_1.FixAcceptor {
28
28
  constructor(config) {
29
- var _a, _b;
29
+ var _a, _b, _c;
30
30
  super((_a = config.description.application) !== null && _a !== void 0 ? _a : null);
31
31
  this.config = config;
32
32
  this.nextId = 0;
33
33
  this.accepted = {};
34
- this.logger = config.logFactory.logger(`${(_b = config.description.application) === null || _b === void 0 ? void 0 : _b.name}:TcpAcceptor`);
34
+ this.logger = config.logFactory.logger(`${(_b = config.description.application) === null || _b === void 0 ? void 0 : _b.name}:TcpAcceptor`, { component: 'TcpAcceptor', app: (_c = config.description.application) === null || _c === void 0 ? void 0 : _c.name, role: 'acceptor' });
35
35
  const tlsOptions = this.tlsOptions();
36
36
  if (tlsOptions) {
37
37
  this.tlsServer();
@@ -1 +1 @@
1
- {"version":3,"file":"tcp-acceptor.js","sourceRoot":"","sources":["../../../src/transport/tcp/tcp-acceptor.ts"],"names":[],"mappings":"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","sourcesContent":["import { TcpDuplex } from '../duplex'\nimport { MsgTransport } from '../factory'\nimport { FixAcceptor } from '../fix-acceptor'\nimport { IJsFixConfig, IJsFixLogger } from '../../config'\nimport { createServer as netCreateServer, Server, Socket } from 'net'\nimport { createServer as tlsCreateServer, TlsOptions, TLSSocket } from 'tls'\nimport { TlsOptionsFactory } from './tls-options-factory'\nimport { describeNegotiated, describePeerCertificate, describeServerOptions } from './tls-diagnostics'\nimport { inject, injectable } from 'tsyringe'\nimport { DITokens } from '../../runtime/di-tokens'\nimport { makeSessionScope } from '../../runtime/session-scope'\n\n@injectable()\nexport class TcpAcceptor extends FixAcceptor {\n private server: Server\n private readonly logger: IJsFixLogger\n private nextId: number = 0\n\n constructor (@inject(DITokens.IJsFixConfig) public readonly config: IJsFixConfig) {\n super(config.description.application ?? null)\n this.logger = config.logFactory.logger(`${config.description.application?.name}:TcpAcceptor`)\n const tlsOptions: TlsOptions | null = this.tlsOptions()\n if (tlsOptions) {\n this.tlsServer()\n } else {\n this.unsecureServer()\n }\n this.server.on('error', (err: Error) => {\n throw err\n })\n }\n\n getId (): number {\n this.nextId++\n return this.nextId\n }\n\n tlsServer (): void {\n try {\n const config: IJsFixConfig = this.config\n const tcp = this.config.description.application?.tcp\n const tlsOptions: TlsOptions | null = tcp?.tls ? TlsOptionsFactory.getTlsOptions(tcp.tls) : null\n this.logger.info('create tls server')\n if (!tlsOptions) return\n this.logger.info(`tls server options ${describeServerOptions(tlsOptions)}`)\n this.server = tlsCreateServer(tlsOptions, (tlsSocket: TLSSocket) => {\n if (tcp?.tls?.enableTrace) {\n this.logger.info('enabling tls session trace')\n tlsSocket.enableTrace()\n }\n this.logger.info(`tls negotiated ${describeNegotiated(tlsSocket)}`)\n this.logger.info(`tls peer certificate ${describePeerCertificate(tlsSocket)}`)\n // mirrors the initiator fix for #94 - an unauthorized peer is only fatal when the\n // caller has not explicitly opted out. the default stays secure: rejectUnauthorized\n // undefined means reject, exactly as before.\n if (!tlsSocket.authorized && tlsOptions.rejectUnauthorized !== false) {\n this.logger.warning(`no transport created, unauthorized tls connection: ${String(tlsSocket.authorizationError)}`)\n tlsSocket.end()\n return\n }\n if (!tlsSocket.authorized) {\n this.logger.warning(`accepting unauthorized tls peer (rejectUnauthorized: false): ${String(tlsSocket.authorizationError)}`)\n }\n tlsSocket.setEncoding('utf8')\n const id: number = this.getId()\n this.logger.info(`tls creates session ${id} authorized ${tlsSocket.authorized}`)\n this.onSocket(id, tlsSocket, config)\n })\n } catch (e) {\n this.logger.error(e as Error)\n throw e\n }\n }\n\n unsecureServer (): void {\n try {\n const config = this.config\n this.logger.info('create unsecured server')\n this.server = netCreateServer((socket: Socket) => {\n const id: number = this.getId()\n this.logger.info(`net creates session ${id}`)\n socket.setNoDelay(true)\n this.onSocket(id, socket, config)\n })\n } catch (e) {\n this.logger.error(e as Error)\n throw e\n }\n }\n\n tlsOptions (): TlsOptions | null {\n const tcp = this.config.description.application?.tcp\n return tcp?.tls ? TlsOptionsFactory.getTlsOptions(tcp?.tls) : null\n }\n\n private onSocket (id: number, socket: Socket, config: IJsFixConfig): void {\n // Every accepted connection gets its own DI scope: its own parse and transmit\n // buffers, its own session description (hence its own SessionId and message\n // store) and its own session message factory. Without this, concurrent clients\n // on one listener share buffers and collide on a single store.\n const sessionConfig = makeSessionScope(config)\n const remote = `${socket.remoteAddress ?? '?'}:${socket.remotePort ?? 0}`\n this.applyKeepAlive(id, socket)\n this.logger.info(`transport ${id} from ${remote} given its own session scope`)\n const transport: MsgTransport = new MsgTransport(id, sessionConfig, new TcpDuplex(socket))\n this.saveTransport(id, transport, remote)\n transport.receiver.on('end', () => {\n this.harvestTransport(id, 'peer ended the connection')\n })\n transport.receiver.on('error', (e: Error) => {\n this.logger.error(e)\n this.harvestTransport(id, `receiver error: ${e.message}`)\n })\n // 'end' only fires on a clean FIN. A socket dropped by a firewall, or one this\n // side destroyed after a failed graceful close, arrives here instead - without\n // it the transport would sit in the map forever.\n socket.on('close', (hadError: boolean) => {\n this.harvestTransport(id, hadError ? 'socket closed after error' : 'socket closed')\n })\n }\n\n /**\n * TCP keep-alive is the only mechanism that detects a peer which has vanished\n * without sending FIN or RST - the half open socket described in issue #153. The\n * session level TestRequest timeout is the protocol level backstop; this is the\n * transport level one.\n */\n private applyKeepAlive (id: number, socket: Socket): void {\n const configured = this.config.description.application?.tcp?.keepAliveMs\n const keepAliveMs = configured ?? TcpAcceptor.defaultKeepAliveMs\n if (keepAliveMs <= 0) {\n this.logger.info(`transport ${id} keep-alive disabled`)\n return\n }\n socket.setKeepAlive(true, keepAliveMs)\n this.logger.info(`transport ${id} keep-alive enabled, initial delay ${keepAliveMs}ms`)\n }\n\n public static defaultKeepAliveMs: number = 30000\n\n public listen (): void {\n const port = this.config.description.application?.tcp?.port\n this.logger.info(`start to listen ${port}`)\n this.server.on('connection', () => {\n this.logger.info('insecure connection established')\n })\n this.server.on('secureConnection', (s) => {\n this.logger.info(`secure connection; client authorized: ${s.authorized}`)\n })\n this.server.listen(port)\n }\n\n public close (callback?: (err?: Error) => void): void {\n const port = this.config.description.application?.tcp?.port ?? -1\n this.logger.info(`close listener on port ${port} with ${this.transportCount} live transport(s)`)\n // Server.close only stops accepting - it waits for existing connections to go.\n // Release them, otherwise a shutdown blocks on whichever counterparty happens to\n // be connected (and forever on one that is half open).\n Object.keys(this.transports).forEach(id => {\n const tid = Number(id)\n this.logger.info(`close: releasing transport ${tid}`)\n this.transports[tid]?.end()\n })\n this.server.close(callback)\n }\n\n /** how many connections this listener currently holds */\n public get transportCount (): number {\n return Object.keys(this.transports).length\n }\n\n /** ids of the live transports, for a periodic census in the application log */\n public liveTransports (): string[] {\n return Object.keys(this.transports).map(id => {\n const meta = this.accepted[id as any]\n const age = meta ? Math.round((Date.now() - meta.at) / 1000) : -1\n return `${id}@${meta?.remote ?? '?'} up ${age}s`\n })\n }\n\n private readonly accepted: Record<number, { remote: string, at: number }> = {}\n\n private saveTransport (tid: number, transport: MsgTransport, remote: string): void {\n this.transports[tid] = transport\n this.accepted[tid] = { remote, at: Date.now() }\n this.logger.info(`new transport id = ${tid} from ${remote} created total transports = ${this.transportCount}`)\n this.emit('transport', transport)\n }\n\n private harvestTransport (tid: number, reason: string): void {\n if (!this.transports[tid]) return\n const meta = this.accepted[tid]\n const lifetime = meta ? Math.round((Date.now() - meta.at) / 1000) : -1\n delete this.transports[tid]\n delete this.accepted[tid]\n this.logger.info(`transport ${tid} from ${meta?.remote ?? '?'} harvested after ${lifetime}s ` +\n `(${reason}) total transports = ${this.transportCount}`)\n this.emit('harvested', tid, reason)\n }\n}\n"]}
1
+ {"version":3,"file":"tcp-acceptor.js","sourceRoot":"","sources":["../../../src/transport/tcp/tcp-acceptor.ts"],"names":[],"mappings":"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","sourcesContent":["import { TcpDuplex } from '../duplex'\nimport { MsgTransport } from '../factory'\nimport { FixAcceptor } from '../fix-acceptor'\nimport { IJsFixConfig, IJsFixLogger } from '../../config'\nimport { createServer as netCreateServer, Server, Socket } from 'net'\nimport { createServer as tlsCreateServer, TlsOptions, TLSSocket } from 'tls'\nimport { TlsOptionsFactory } from './tls-options-factory'\nimport { describeNegotiated, describePeerCertificate, describeServerOptions } from './tls-diagnostics'\nimport { inject, injectable } from 'tsyringe'\nimport { DITokens } from '../../runtime/di-tokens'\nimport { makeSessionScope } from '../../runtime/session-scope'\n\n@injectable()\nexport class TcpAcceptor extends FixAcceptor {\n private server: Server\n private readonly logger: IJsFixLogger\n private nextId: number = 0\n\n constructor (@inject(DITokens.IJsFixConfig) public readonly config: IJsFixConfig) {\n super(config.description.application ?? null)\n this.logger = config.logFactory.logger(`${config.description.application?.name}:TcpAcceptor`,\n { component: 'TcpAcceptor', app: config.description.application?.name, role: 'acceptor' })\n const tlsOptions: TlsOptions | null = this.tlsOptions()\n if (tlsOptions) {\n this.tlsServer()\n } else {\n this.unsecureServer()\n }\n this.server.on('error', (err: Error) => {\n throw err\n })\n }\n\n getId (): number {\n this.nextId++\n return this.nextId\n }\n\n tlsServer (): void {\n try {\n const config: IJsFixConfig = this.config\n const tcp = this.config.description.application?.tcp\n const tlsOptions: TlsOptions | null = tcp?.tls ? TlsOptionsFactory.getTlsOptions(tcp.tls) : null\n this.logger.info('create tls server')\n if (!tlsOptions) return\n this.logger.info(`tls server options ${describeServerOptions(tlsOptions)}`)\n this.server = tlsCreateServer(tlsOptions, (tlsSocket: TLSSocket) => {\n if (tcp?.tls?.enableTrace) {\n this.logger.info('enabling tls session trace')\n tlsSocket.enableTrace()\n }\n this.logger.info(`tls negotiated ${describeNegotiated(tlsSocket)}`)\n this.logger.info(`tls peer certificate ${describePeerCertificate(tlsSocket)}`)\n // mirrors the initiator fix for #94 - an unauthorized peer is only fatal when the\n // caller has not explicitly opted out. the default stays secure: rejectUnauthorized\n // undefined means reject, exactly as before.\n if (!tlsSocket.authorized && tlsOptions.rejectUnauthorized !== false) {\n this.logger.warning(`no transport created, unauthorized tls connection: ${String(tlsSocket.authorizationError)}`)\n tlsSocket.end()\n return\n }\n if (!tlsSocket.authorized) {\n this.logger.warning(`accepting unauthorized tls peer (rejectUnauthorized: false): ${String(tlsSocket.authorizationError)}`)\n }\n tlsSocket.setEncoding('utf8')\n const id: number = this.getId()\n this.logger.info(`tls creates session ${id} authorized ${tlsSocket.authorized}`)\n this.onSocket(id, tlsSocket, config)\n })\n } catch (e) {\n this.logger.error(e as Error)\n throw e\n }\n }\n\n unsecureServer (): void {\n try {\n const config = this.config\n this.logger.info('create unsecured server')\n this.server = netCreateServer((socket: Socket) => {\n const id: number = this.getId()\n this.logger.info(`net creates session ${id}`)\n socket.setNoDelay(true)\n this.onSocket(id, socket, config)\n })\n } catch (e) {\n this.logger.error(e as Error)\n throw e\n }\n }\n\n tlsOptions (): TlsOptions | null {\n const tcp = this.config.description.application?.tcp\n return tcp?.tls ? TlsOptionsFactory.getTlsOptions(tcp?.tls) : null\n }\n\n private onSocket (id: number, socket: Socket, config: IJsFixConfig): void {\n // Every accepted connection gets its own DI scope: its own parse and transmit\n // buffers, its own session description (hence its own SessionId and message\n // store) and its own session message factory. Without this, concurrent clients\n // on one listener share buffers and collide on a single store.\n const sessionConfig = makeSessionScope(config)\n const remote = `${socket.remoteAddress ?? '?'}:${socket.remotePort ?? 0}`\n this.applyKeepAlive(id, socket)\n this.logger.info(`transport ${id} from ${remote} given its own session scope`)\n const transport: MsgTransport = new MsgTransport(id, sessionConfig, new TcpDuplex(socket))\n this.saveTransport(id, transport, remote)\n transport.receiver.on('end', () => {\n this.harvestTransport(id, 'peer ended the connection')\n })\n transport.receiver.on('error', (e: Error) => {\n this.logger.error(e)\n this.harvestTransport(id, `receiver error: ${e.message}`)\n })\n // 'end' only fires on a clean FIN. A socket dropped by a firewall, or one this\n // side destroyed after a failed graceful close, arrives here instead - without\n // it the transport would sit in the map forever.\n socket.on('close', (hadError: boolean) => {\n this.harvestTransport(id, hadError ? 'socket closed after error' : 'socket closed')\n })\n }\n\n /**\n * TCP keep-alive is the only mechanism that detects a peer which has vanished\n * without sending FIN or RST - the half open socket described in issue #153. The\n * session level TestRequest timeout is the protocol level backstop; this is the\n * transport level one.\n */\n private applyKeepAlive (id: number, socket: Socket): void {\n const configured = this.config.description.application?.tcp?.keepAliveMs\n const keepAliveMs = configured ?? TcpAcceptor.defaultKeepAliveMs\n if (keepAliveMs <= 0) {\n this.logger.info(`transport ${id} keep-alive disabled`)\n return\n }\n socket.setKeepAlive(true, keepAliveMs)\n this.logger.info(`transport ${id} keep-alive enabled, initial delay ${keepAliveMs}ms`)\n }\n\n public static defaultKeepAliveMs: number = 30000\n\n public listen (): void {\n const port = this.config.description.application?.tcp?.port\n this.logger.info(`start to listen ${port}`)\n this.server.on('connection', () => {\n this.logger.info('insecure connection established')\n })\n this.server.on('secureConnection', (s) => {\n this.logger.info(`secure connection; client authorized: ${s.authorized}`)\n })\n this.server.listen(port)\n }\n\n public close (callback?: (err?: Error) => void): void {\n const port = this.config.description.application?.tcp?.port ?? -1\n this.logger.info(`close listener on port ${port} with ${this.transportCount} live transport(s)`)\n // Server.close only stops accepting - it waits for existing connections to go.\n // Release them, otherwise a shutdown blocks on whichever counterparty happens to\n // be connected (and forever on one that is half open).\n Object.keys(this.transports).forEach(id => {\n const tid = Number(id)\n this.logger.info(`close: releasing transport ${tid}`)\n this.transports[tid]?.end()\n })\n this.server.close(callback)\n }\n\n /** how many connections this listener currently holds */\n public get transportCount (): number {\n return Object.keys(this.transports).length\n }\n\n /** ids of the live transports, for a periodic census in the application log */\n public liveTransports (): string[] {\n return Object.keys(this.transports).map(id => {\n const meta = this.accepted[id as any]\n const age = meta ? Math.round((Date.now() - meta.at) / 1000) : -1\n return `${id}@${meta?.remote ?? '?'} up ${age}s`\n })\n }\n\n private readonly accepted: Record<number, { remote: string, at: number }> = {}\n\n private saveTransport (tid: number, transport: MsgTransport, remote: string): void {\n this.transports[tid] = transport\n this.accepted[tid] = { remote, at: Date.now() }\n this.logger.info(`new transport id = ${tid} from ${remote} created total transports = ${this.transportCount}`)\n this.emit('transport', transport)\n }\n\n private harvestTransport (tid: number, reason: string): void {\n if (!this.transports[tid]) return\n const meta = this.accepted[tid]\n const lifetime = meta ? Math.round((Date.now() - meta.at) / 1000) : -1\n delete this.transports[tid]\n delete this.accepted[tid]\n this.logger.info(`transport ${tid} from ${meta?.remote ?? '?'} harvested after ${lifetime}s ` +\n `(${reason}) total transports = ${this.transportCount}`)\n this.emit('harvested', tid, reason)\n }\n}\n"]}
@@ -38,7 +38,7 @@ let TcpInitiatorConnector = TcpInitiatorConnector_1 = class TcpInitiatorConnecto
38
38
  start() {
39
39
  return __awaiter(this, arguments, void 0, function* (reconnectTimeout = 0) {
40
40
  return yield new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
41
- const logger = this.config.logFactory.logger('initiator');
41
+ const logger = this.config.logFactory.logger('initiator', { component: 'TcpInitiatorConnector', role: 'initiator' });
42
42
  const sessionContainer = this.config.sessionContainer;
43
43
  if (!sessionContainer.isRegistered(di_tokens_1.DITokens.FixSession)) {
44
44
  reject(new Error(`application must register a DI token '${di_tokens_1.DITokens.FixSession}' - see src/sample`));
@@ -86,7 +86,7 @@ let TcpInitiatorConnector = TcpInitiatorConnector_1 = class TcpInitiatorConnecto
86
86
  }
87
87
  connect(initiatorSession) {
88
88
  return __awaiter(this, void 0, void 0, function* () {
89
- const logger = this.config.logFactory.logger('initiator');
89
+ const logger = this.config.logFactory.logger('initiator', { component: 'TcpInitiatorConnector', role: 'initiator' });
90
90
  const initiator = this.config.sessionContainer.resolve(tcp_initiator_1.TcpInitiator);
91
91
  logger.info('connecting ...');
92
92
  const initiatorTransport = yield initiator.connect(this.connectTimeoutSecs);
@@ -1 +1 @@
1
- {"version":3,"file":"tcp-initiator-connector.js","sourceRoot":"","sources":["../../../src/transport/tcp/tcp-initiator-connector.ts"],"names":[],"mappings":"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","sourcesContent":["import { IJsFixConfig } from '../../config'\nimport { FixInitiator } from '../fix-initiator'\nimport { TcpInitiator } from './tcp-initiator'\nimport { MsgTransport } from '../factory'\nimport { FixSession } from '../session/fix-session'\nimport { inject, injectable } from 'tsyringe'\nimport { DITokens } from '../../runtime/di-tokens'\nimport { FixEntity } from '../fix-entity'\n\n@injectable()\nexport class TcpInitiatorConnector extends FixEntity {\n public static readonly DefaultConnectTimeoutSecs = 22\n\n public connectTimeoutSecs: number = TcpInitiatorConnector.DefaultConnectTimeoutSecs\n\n constructor (@inject(DITokens.IJsFixConfig) public readonly config: IJsFixConfig) {\n super(config)\n this.connectTimeoutSecs = config.description.application?.connectTimeoutSeconds ?? this.connectTimeoutSecs\n }\n\n async start (reconnectTimeout: number = 0): Promise<any> {\n\n return await new Promise<any>(async (resolve, reject) => {\n const logger = this.config.logFactory.logger('initiator')\n const sessionContainer = this.config.sessionContainer\n if (!sessionContainer.isRegistered(DITokens.FixSession)) {\n reject(new Error(`application must register a DI token '${DITokens.FixSession}' - see src/sample`))\n }\n logger.info(`create session with DI Token ${DITokens.FixSession}`)\n const initiatorSession = sessionContainer.resolve<FixSession>(DITokens.FixSession)\n let connecting: boolean = true\n while (connecting) {\n try {\n await this.connect(initiatorSession)\n logger.info('session has ended')\n connecting = false\n resolve(true)\n } catch (e) {\n if (!reconnectTimeout) {\n connecting = false\n reject(e)\n } else {\n logger.info(`waiting ${reconnectTimeout} to reconnect following error`)\n await this.delay(reconnectTimeout)\n }\n }\n }\n })\n }\n\n async delay (p: number): Promise<any> {\n return await new Promise<any>((resolve, reject) => {\n try {\n if (!p) {\n resolve(true)\n }\n setTimeout(() => {\n resolve(true)\n }, p)\n } catch (e) {\n reject(e)\n }\n })\n }\n\n async connect (initiatorSession: FixSession): Promise<any> {\n const logger = this.config.logFactory.logger('initiator')\n const initiator: FixInitiator = this.config.sessionContainer.resolve<FixInitiator>(TcpInitiator)\n logger.info('connecting ...')\n const initiatorTransport: MsgTransport = await initiator.connect(this.connectTimeoutSecs)\n logger.info('... connected, run session')\n await initiatorSession.run(initiatorTransport)\n logger.info('ends')\n return true\n }\n}\n"]}
1
+ {"version":3,"file":"tcp-initiator-connector.js","sourceRoot":"","sources":["../../../src/transport/tcp/tcp-initiator-connector.ts"],"names":[],"mappings":"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","sourcesContent":["import { IJsFixConfig } from '../../config'\nimport { FixInitiator } from '../fix-initiator'\nimport { TcpInitiator } from './tcp-initiator'\nimport { MsgTransport } from '../factory'\nimport { FixSession } from '../session/fix-session'\nimport { inject, injectable } from 'tsyringe'\nimport { DITokens } from '../../runtime/di-tokens'\nimport { FixEntity } from '../fix-entity'\n\n@injectable()\nexport class TcpInitiatorConnector extends FixEntity {\n public static readonly DefaultConnectTimeoutSecs = 22\n\n public connectTimeoutSecs: number = TcpInitiatorConnector.DefaultConnectTimeoutSecs\n\n constructor (@inject(DITokens.IJsFixConfig) public readonly config: IJsFixConfig) {\n super(config)\n this.connectTimeoutSecs = config.description.application?.connectTimeoutSeconds ?? this.connectTimeoutSecs\n }\n\n async start (reconnectTimeout: number = 0): Promise<any> {\n\n return await new Promise<any>(async (resolve, reject) => {\n const logger = this.config.logFactory.logger('initiator',\n { component: 'TcpInitiatorConnector', role: 'initiator' })\n const sessionContainer = this.config.sessionContainer\n if (!sessionContainer.isRegistered(DITokens.FixSession)) {\n reject(new Error(`application must register a DI token '${DITokens.FixSession}' - see src/sample`))\n }\n logger.info(`create session with DI Token ${DITokens.FixSession}`)\n const initiatorSession = sessionContainer.resolve<FixSession>(DITokens.FixSession)\n let connecting: boolean = true\n while (connecting) {\n try {\n await this.connect(initiatorSession)\n logger.info('session has ended')\n connecting = false\n resolve(true)\n } catch (e) {\n if (!reconnectTimeout) {\n connecting = false\n reject(e)\n } else {\n logger.info(`waiting ${reconnectTimeout} to reconnect following error`)\n await this.delay(reconnectTimeout)\n }\n }\n }\n })\n }\n\n async delay (p: number): Promise<any> {\n return await new Promise<any>((resolve, reject) => {\n try {\n if (!p) {\n resolve(true)\n }\n setTimeout(() => {\n resolve(true)\n }, p)\n } catch (e) {\n reject(e)\n }\n })\n }\n\n async connect (initiatorSession: FixSession): Promise<any> {\n const logger = this.config.logFactory.logger('initiator',\n { component: 'TcpInitiatorConnector', role: 'initiator' })\n const initiator: FixInitiator = this.config.sessionContainer.resolve<FixInitiator>(TcpInitiator)\n logger.info('connecting ...')\n const initiatorTransport: MsgTransport = await initiator.connect(this.connectTimeoutSecs)\n logger.info('... connected, run session')\n await initiatorSession.run(initiatorTransport)\n logger.info('ends')\n return true\n }\n}\n"]}
@@ -48,7 +48,7 @@ let TcpInitiator = class TcpInitiator extends fix_initiator_1.FixInitiator {
48
48
  this.th = null;
49
49
  this.timeoutTh = null;
50
50
  const name = (_c = (_b = this.application) === null || _b === void 0 ? void 0 : _b.name) !== null && _c !== void 0 ? _c : 'initiator';
51
- this.logger = jsFixConfig.logFactory.logger(`${name}:TcpInitiator`);
51
+ this.logger = jsFixConfig.logFactory.logger(`${name}:TcpInitiator`, { component: 'TcpInitiator', app: name, role: 'initiator' });
52
52
  if (!this.application) {
53
53
  throw new Error('no application in session description.');
54
54
  }
@@ -1 +1 @@
1
- {"version":3,"file":"tcp-initiator.js","sourceRoot":"","sources":["../../../src/transport/tcp/tcp-initiator.ts"],"names":[],"mappings":"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","sourcesContent":["import { FixInitiator } from '../fix-initiator'\nimport { MsgTransport } from '../factory'\n\nimport { IJsFixConfig, IJsFixLogger } from '../../config'\nimport { TcpDuplex, FixDuplex } from '../duplex'\n\nimport { connect as tlsConnect, ConnectionOptions, TLSSocket } from 'tls'\nimport { createConnection } from 'net'\nimport Timeout = NodeJS.Timeout\nimport { TlsOptionsFactory } from './tls-options-factory'\nimport { describeConnectionOptions, describeNegotiated, describePeerCertificate } from './tls-diagnostics'\nimport { describeConnectError, withConnectReason } from './connect-error'\nimport { inject, injectable } from 'tsyringe'\nimport { DITokens } from '../../runtime/di-tokens'\nimport { ITcpTransportDescription } from './tcp-transport-description'\n\nexport enum InitiatorState {\n Idle = 1,\n Connecting = 2,\n Connected = 3,\n Stopped = 4\n}\n\n@injectable()\nexport class TcpInitiator extends FixInitiator {\n public tcp: ITcpTransportDescription | null\n public state: InitiatorState = InitiatorState.Idle\n private readonly logger: IJsFixLogger\n private duplex: FixDuplex\n private th: Timeout | null = null\n private timeoutTh: Timeout | null = null\n\n constructor (@inject(DITokens.IJsFixConfig) public readonly jsFixConfig: IJsFixConfig) {\n super(jsFixConfig.description.application ?? null)\n const name = this.application?.name ?? 'initiator'\n this.logger = jsFixConfig.logFactory.logger(`${name}:TcpInitiator`)\n if (!this.application) {\n throw new Error('no application in session description.')\n }\n this.tcp = this.application.tcp ?? null\n if (!this.tcp) {\n throw new Error('no tcp in session description need tcp { host: hostname, port: port }')\n }\n }\n\n public end (): void {\n this.clearTimer()\n switch (this.state) {\n case InitiatorState.Connected: {\n this.logger.info('end')\n this.duplex.end()\n this.state = InitiatorState.Stopped\n break\n }\n\n default: {\n this.logger.info(`end :state ${this.state}`)\n this.state = InitiatorState.Stopped\n break\n }\n }\n }\n\n public async connect (timeoutSeconds: number): Promise<MsgTransport> {\n return await new Promise<MsgTransport>(async (resolve, reject) => {\n switch (this.state) {\n case InitiatorState.Idle: {\n this.state = InitiatorState.Connecting\n this.logger.info(`connecting with timeout ${timeoutSeconds}`)\n this.tryConnect()\n .then((t: MsgTransport) => { resolve(t) })\n .catch((first: Error) => {\n // carry the first failure into repeatConnect. it used to be discarded, so a\n // connect() whose timeout expires before the first retry interval fires - the\n // default case, timeout and reconnectSeconds are both 5 - reported a generic\n // \"timeout whilst connecting\" and threw away the reason. see #94.\n this.logger.info(`first connect attempt failed: ${describeConnectError(first)}`)\n this.repeatConnect(timeoutSeconds, first)\n .then((t: MsgTransport) => { resolve(t) })\n .catch((e: Error) => { reject(e) })\n })\n break\n }\n\n default: {\n const e: Error = new Error('connect not valid from non idle state')\n this.logger.warning(`rejecting from state ${this.state}`)\n reject(e)\n }\n }\n })\n }\n\n private async unsecureDuplex (): Promise<TcpDuplex> {\n const tcp = this.tcp\n return await new Promise<TcpDuplex>((resolve, reject) => {\n try {\n this.logger.info('unsecureDuplex try to connect to endPoint')\n const socket = tcp\n ? createConnection(tcp, () => {\n try {\n this.logger.info('net.createConnection cb, resolving')\n if (socket) {\n const tcpDuplex = new TcpDuplex(socket)\n resolve(tcpDuplex)\n } else {\n reject(new Error('no socket in tcp initiator'))\n }\n } catch (e) {\n reject(e)\n }\n })\n : null\n socket?.on('error', (err) => {\n reject(err)\n })\n } catch (e) {\n reject(e)\n }\n })\n }\n\n private async tlsDuplex (): Promise<TcpDuplex > {\n return await new Promise<TcpDuplex>((resolve, reject) => {\n let tlsSocket: TLSSocket | null = null\n const tcp = this.tcp\n const connectionOptions: ConnectionOptions | null = tcp ? TlsOptionsFactory.getTlsConnectionOptions(tcp) : null\n if (connectionOptions) {\n try {\n this.logger.info(`tls connect with ${describeConnectionOptions(connectionOptions)}`)\n tlsSocket = tlsConnect(connectionOptions, () => {\n if (!tlsSocket) return null\n this.logger.info(`client connected ${tlsSocket.authorized ? 'authorized' : 'unauthorized'}`)\n this.logger.info(`tls negotiated ${describeNegotiated(tlsSocket)}`)\n this.logger.info(`tls peer certificate ${describePeerCertificate(tlsSocket)}`)\n // https://github.com/TimelordUK/jspurefix/issues/94 - honour rejectUnauthorized:\n // false rather than always rejecting on an unauthorized cert. Node's tls.connect()\n // never throws for an unauthorized peer by itself; it's the caller's job to decide\n // what to do with tlsSocket.authorized, and rejectUnauthorized is how a caller opts\n // out of that check - so only reject here when the caller didn't opt out.\n if (!tlsSocket.authorized && connectionOptions.rejectUnauthorized !== false) {\n const error = tlsSocket.authorizationError\n this.logger.warning(`rejecting from state ${this.state} authorizationError ${error}`)\n tlsSocket.end()\n reject(error)\n } else {\n if (!tlsSocket.authorized) {\n this.logger.warning(`proceeding despite unauthorized TLS peer (rejectUnauthorized: false): ${tlsSocket.authorizationError}`)\n }\n tlsSocket.setEncoding('utf8')\n const tlsDuplex = new TcpDuplex(tlsSocket)\n if (tcp?.tls?.enableTrace) {\n this.logger.info('enabling tls session trace')\n tlsSocket.enableTrace()\n }\n this.logger.info('tlsDuplex resolving')\n resolve(tlsDuplex)\n }\n })\n tlsSocket.on('error', (err) => {\n // previously silent - the socket error is the real reason a tls session failed,\n // and without it the caller only ever sees repeatConnect's generic timeout.\n this.logger.warning(`tls socket error from state ${this.state}: ${err.message}`)\n reject(err)\n })\n } catch (e) {\n reject(e)\n }\n }\n })\n }\n\n private async tryConnect (): Promise < MsgTransport > {\n return await new Promise<MsgTransport>((resolve, reject) => {\n const tcp = this.tcp\n const connectionOptions: ConnectionOptions | null = tcp ? TlsOptionsFactory.getTlsConnectionOptions(tcp) : null\n const connector = connectionOptions ? this.tlsDuplex() : this.unsecureDuplex()\n this.logger.info(`tryConnect ${tcp?.host}:${tcp?.port}`)\n connector.then(duplex => {\n this.duplex = duplex\n resolve(new MsgTransport(0, this.jsFixConfig, duplex))\n }).catch(e => {\n reject(e)\n })\n })\n }\n\n public clearTimer (): void {\n if (this.th) {\n clearInterval(this.th)\n this.th = null\n }\n if (this.timeoutTh) {\n clearTimeout(this.timeoutTh)\n this.timeoutTh = null\n }\n }\n\n private async repeatConnect (timeoutSeconds: number, initialError?: Error): Promise < MsgTransport > {\n return await new Promise<MsgTransport>(async (resolve, reject) => {\n const application = this.application\n const reconnectSeconds = application?.reconnectSeconds ?? 5\n let retries = 0\n let lastError: Error | undefined = initialError\n const name = application?.name ?? 'initiator'\n this.th = setInterval(() => {\n ++retries\n this.tryConnect()\n .then((t: MsgTransport) => {\n this.state = InitiatorState.Connected\n this.clearTimer()\n resolve(t)\n }).catch((e: Error) => {\n lastError = e\n this.logger.info(`${name}: retries ${retries} ${describeConnectError(e)}`)\n })\n }, reconnectSeconds * 1000)\n // a plain timer, not a promisified one: promisify(setTimeout) hands back no\n // handle, so this deadline could never be cancelled. Connecting successfully\n // cleared the retry interval and left this one running to its full term - up to\n // the 60s an initiator allows - holding the event loop open long after the\n // session was up, and stopping a process (or a test runner) from exiting.\n this.timeoutTh = setTimeout(() => {\n this.clearTimer()\n this.state = InitiatorState.Stopped\n // this error is what the application catches from run() - so give it a message\n // it can print. An AggregateError arrives with none at all, which meant a\n // caller handling a failed connect had nothing to report but an empty string.\n const e = withConnectReason(lastError ?? new Error(`${name}: timeout of ${timeoutSeconds} whilst connecting`))\n this.logger.warning(`${name}: giving up after ${timeoutSeconds}s and ${retries} retries, last error: ${e.message}`)\n reject(e)\n }, timeoutSeconds * 1000)\n })\n }\n}\n"]}
1
+ {"version":3,"file":"tcp-initiator.js","sourceRoot":"","sources":["../../../src/transport/tcp/tcp-initiator.ts"],"names":[],"mappings":"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","sourcesContent":["import { FixInitiator } from '../fix-initiator'\nimport { MsgTransport } from '../factory'\n\nimport { IJsFixConfig, IJsFixLogger } from '../../config'\nimport { TcpDuplex, FixDuplex } from '../duplex'\n\nimport { connect as tlsConnect, ConnectionOptions, TLSSocket } from 'tls'\nimport { createConnection } from 'net'\nimport Timeout = NodeJS.Timeout\nimport { TlsOptionsFactory } from './tls-options-factory'\nimport { describeConnectionOptions, describeNegotiated, describePeerCertificate } from './tls-diagnostics'\nimport { describeConnectError, withConnectReason } from './connect-error'\nimport { inject, injectable } from 'tsyringe'\nimport { DITokens } from '../../runtime/di-tokens'\nimport { ITcpTransportDescription } from './tcp-transport-description'\n\nexport enum InitiatorState {\n Idle = 1,\n Connecting = 2,\n Connected = 3,\n Stopped = 4\n}\n\n@injectable()\nexport class TcpInitiator extends FixInitiator {\n public tcp: ITcpTransportDescription | null\n public state: InitiatorState = InitiatorState.Idle\n private readonly logger: IJsFixLogger\n private duplex: FixDuplex\n private th: Timeout | null = null\n private timeoutTh: Timeout | null = null\n\n constructor (@inject(DITokens.IJsFixConfig) public readonly jsFixConfig: IJsFixConfig) {\n super(jsFixConfig.description.application ?? null)\n const name = this.application?.name ?? 'initiator'\n this.logger = jsFixConfig.logFactory.logger(`${name}:TcpInitiator`,\n { component: 'TcpInitiator', app: name, role: 'initiator' })\n if (!this.application) {\n throw new Error('no application in session description.')\n }\n this.tcp = this.application.tcp ?? null\n if (!this.tcp) {\n throw new Error('no tcp in session description need tcp { host: hostname, port: port }')\n }\n }\n\n public end (): void {\n this.clearTimer()\n switch (this.state) {\n case InitiatorState.Connected: {\n this.logger.info('end')\n this.duplex.end()\n this.state = InitiatorState.Stopped\n break\n }\n\n default: {\n this.logger.info(`end :state ${this.state}`)\n this.state = InitiatorState.Stopped\n break\n }\n }\n }\n\n public async connect (timeoutSeconds: number): Promise<MsgTransport> {\n return await new Promise<MsgTransport>(async (resolve, reject) => {\n switch (this.state) {\n case InitiatorState.Idle: {\n this.state = InitiatorState.Connecting\n this.logger.info(`connecting with timeout ${timeoutSeconds}`)\n this.tryConnect()\n .then((t: MsgTransport) => { resolve(t) })\n .catch((first: Error) => {\n // carry the first failure into repeatConnect. it used to be discarded, so a\n // connect() whose timeout expires before the first retry interval fires - the\n // default case, timeout and reconnectSeconds are both 5 - reported a generic\n // \"timeout whilst connecting\" and threw away the reason. see #94.\n this.logger.info(`first connect attempt failed: ${describeConnectError(first)}`)\n this.repeatConnect(timeoutSeconds, first)\n .then((t: MsgTransport) => { resolve(t) })\n .catch((e: Error) => { reject(e) })\n })\n break\n }\n\n default: {\n const e: Error = new Error('connect not valid from non idle state')\n this.logger.warning(`rejecting from state ${this.state}`)\n reject(e)\n }\n }\n })\n }\n\n private async unsecureDuplex (): Promise<TcpDuplex> {\n const tcp = this.tcp\n return await new Promise<TcpDuplex>((resolve, reject) => {\n try {\n this.logger.info('unsecureDuplex try to connect to endPoint')\n const socket = tcp\n ? createConnection(tcp, () => {\n try {\n this.logger.info('net.createConnection cb, resolving')\n if (socket) {\n const tcpDuplex = new TcpDuplex(socket)\n resolve(tcpDuplex)\n } else {\n reject(new Error('no socket in tcp initiator'))\n }\n } catch (e) {\n reject(e)\n }\n })\n : null\n socket?.on('error', (err) => {\n reject(err)\n })\n } catch (e) {\n reject(e)\n }\n })\n }\n\n private async tlsDuplex (): Promise<TcpDuplex > {\n return await new Promise<TcpDuplex>((resolve, reject) => {\n let tlsSocket: TLSSocket | null = null\n const tcp = this.tcp\n const connectionOptions: ConnectionOptions | null = tcp ? TlsOptionsFactory.getTlsConnectionOptions(tcp) : null\n if (connectionOptions) {\n try {\n this.logger.info(`tls connect with ${describeConnectionOptions(connectionOptions)}`)\n tlsSocket = tlsConnect(connectionOptions, () => {\n if (!tlsSocket) return null\n this.logger.info(`client connected ${tlsSocket.authorized ? 'authorized' : 'unauthorized'}`)\n this.logger.info(`tls negotiated ${describeNegotiated(tlsSocket)}`)\n this.logger.info(`tls peer certificate ${describePeerCertificate(tlsSocket)}`)\n // https://github.com/TimelordUK/jspurefix/issues/94 - honour rejectUnauthorized:\n // false rather than always rejecting on an unauthorized cert. Node's tls.connect()\n // never throws for an unauthorized peer by itself; it's the caller's job to decide\n // what to do with tlsSocket.authorized, and rejectUnauthorized is how a caller opts\n // out of that check - so only reject here when the caller didn't opt out.\n if (!tlsSocket.authorized && connectionOptions.rejectUnauthorized !== false) {\n const error = tlsSocket.authorizationError\n this.logger.warning(`rejecting from state ${this.state} authorizationError ${error}`)\n tlsSocket.end()\n reject(error)\n } else {\n if (!tlsSocket.authorized) {\n this.logger.warning(`proceeding despite unauthorized TLS peer (rejectUnauthorized: false): ${tlsSocket.authorizationError}`)\n }\n tlsSocket.setEncoding('utf8')\n const tlsDuplex = new TcpDuplex(tlsSocket)\n if (tcp?.tls?.enableTrace) {\n this.logger.info('enabling tls session trace')\n tlsSocket.enableTrace()\n }\n this.logger.info('tlsDuplex resolving')\n resolve(tlsDuplex)\n }\n })\n tlsSocket.on('error', (err) => {\n // previously silent - the socket error is the real reason a tls session failed,\n // and without it the caller only ever sees repeatConnect's generic timeout.\n this.logger.warning(`tls socket error from state ${this.state}: ${err.message}`)\n reject(err)\n })\n } catch (e) {\n reject(e)\n }\n }\n })\n }\n\n private async tryConnect (): Promise < MsgTransport > {\n return await new Promise<MsgTransport>((resolve, reject) => {\n const tcp = this.tcp\n const connectionOptions: ConnectionOptions | null = tcp ? TlsOptionsFactory.getTlsConnectionOptions(tcp) : null\n const connector = connectionOptions ? this.tlsDuplex() : this.unsecureDuplex()\n this.logger.info(`tryConnect ${tcp?.host}:${tcp?.port}`)\n connector.then(duplex => {\n this.duplex = duplex\n resolve(new MsgTransport(0, this.jsFixConfig, duplex))\n }).catch(e => {\n reject(e)\n })\n })\n }\n\n public clearTimer (): void {\n if (this.th) {\n clearInterval(this.th)\n this.th = null\n }\n if (this.timeoutTh) {\n clearTimeout(this.timeoutTh)\n this.timeoutTh = null\n }\n }\n\n private async repeatConnect (timeoutSeconds: number, initialError?: Error): Promise < MsgTransport > {\n return await new Promise<MsgTransport>(async (resolve, reject) => {\n const application = this.application\n const reconnectSeconds = application?.reconnectSeconds ?? 5\n let retries = 0\n let lastError: Error | undefined = initialError\n const name = application?.name ?? 'initiator'\n this.th = setInterval(() => {\n ++retries\n this.tryConnect()\n .then((t: MsgTransport) => {\n this.state = InitiatorState.Connected\n this.clearTimer()\n resolve(t)\n }).catch((e: Error) => {\n lastError = e\n this.logger.info(`${name}: retries ${retries} ${describeConnectError(e)}`)\n })\n }, reconnectSeconds * 1000)\n // a plain timer, not a promisified one: promisify(setTimeout) hands back no\n // handle, so this deadline could never be cancelled. Connecting successfully\n // cleared the retry interval and left this one running to its full term - up to\n // the 60s an initiator allows - holding the event loop open long after the\n // session was up, and stopping a process (or a test runner) from exiting.\n this.timeoutTh = setTimeout(() => {\n this.clearTimer()\n this.state = InitiatorState.Stopped\n // this error is what the application catches from run() - so give it a message\n // it can print. An AggregateError arrives with none at all, which meant a\n // caller handling a failed connect had nothing to report but an empty string.\n const e = withConnectReason(lastError ?? new Error(`${name}: timeout of ${timeoutSeconds} whilst connecting`))\n this.logger.warning(`${name}: giving up after ${timeoutSeconds}s and ${retries} retries, last error: ${e.message}`)\n reject(e)\n }, timeoutSeconds * 1000)\n })\n }\n}\n"]}
@@ -0,0 +1,346 @@
1
+ # Instrumentation — design note
2
+
3
+ **Status: design, nothing implemented.** This is the shape being argued about before any
4
+ code is written, because the contract has to be right in two engines at once. Comments
5
+ and disagreement welcome on the tracking issue.
6
+
7
+ The goal is that an operator running jspurefix in production can answer, without adding
8
+ their own code: is the session up, how much is going through it, what is it costing me,
9
+ and has anything started going wrong. Today they can answer none of those except by
10
+ reading log prose.
11
+
12
+ ## What this is not
13
+
14
+ [`docs/benchmarking.md`](benchmarking.md) already measures parser throughput and
15
+ allocation. That is **offline**: `jsfix-bench` drives synthetic bytes under
16
+ `--expose-gc` so a change to a hot path can be argued about with a number.
17
+
18
+ This note is about the **live** engine — the same quantities, in a running process, at a
19
+ cost low enough to leave switched on. The two should agree on names wherever they
20
+ measure the same thing, so a bench figure and a production figure can be put side by
21
+ side.
22
+
23
+ ## Principles
24
+
25
+ 1. **The snapshot is the contract.** Prometheus, OpenTelemetry, a JSON endpoint and a
26
+ CLI table are four *renderings* of one data structure. Design that structure; the
27
+ renderings are then cheap, independent, and none of them is in the way of the others.
28
+ 2. **Zero cost when off.** A disabled sink is a no-op method call, never a string format
29
+ or an object allocation. `EmptyLogger` is the existing precedent.
30
+ 3. **Nothing new in `dependencies`.** See [packaging](#packaging-boundary) — this is the
31
+ trap most likely to be walked into.
32
+ 4. **One spec, two engines.** Metric names and semantics are defined here and
33
+ implemented identically in cspurefix. A shared dashboard is most of the value.
34
+
35
+ ## Layer 0 — the snapshot
36
+
37
+ A plain object, no dependencies, produced on demand. Everything else renders it.
38
+
39
+ There is already a rough version of this: `TcpAcceptor` logs an *acceptor census*
40
+ (`transports=1 [1@::1:57598 up 0s] sessions=0 []`). It is a stats snapshot that only
41
+ knows how to be a log line.
42
+
43
+ ```ts
44
+ interface ISessionStats {
45
+ sessionId: string // BeginString-Sender-Target
46
+ peerCompId: string
47
+ role: 'initiator' | 'acceptor'
48
+ state: SessionState // the enum, not a rendered string
49
+ upSince: Date | null
50
+ // sequence
51
+ lastSentSeqNum: number
52
+ lastPeerMsgSeqNum: number
53
+ // traffic, since process start
54
+ msgsSent: number
55
+ msgsReceived: number
56
+ bytesSent: number
57
+ bytesReceived: number
58
+ // health
59
+ resendRequestsSent: number
60
+ resendRequestsReceived: number
61
+ gapsDetected: number
62
+ reconnects: number
63
+ lastReceivedAt: Date | null
64
+ lastSentAt: Date | null
65
+ }
66
+ ```
67
+
68
+ Gauges are read from state that already exists — `FixSessionState` holds
69
+ `lastPeerMsgSeqNum`, `LastSentAt`, `lastReceivedAt`, `peerCompId`; `SessionState` is the
70
+ enum; `SessionRegistry` knows the live set. Counters are the new part.
71
+
72
+ **Open question.** Whether the snapshot is per-session with an aggregate rolled up by the
73
+ registry, or one flat structure. Per-session with rollup is more useful to a venue and
74
+ more work; a single acceptor with one counterparty gets the same answer either way.
75
+
76
+ ## Layer 1 — chokepoints
77
+
78
+ Counters are pushed through a narrow sink with a no-op default. The seams already exist;
79
+ none of these needs a new abstraction to hang off.
80
+
81
+ | what | where (jspurefix) | notes for the port |
82
+ | --- | --- | --- |
83
+ | bytes in, parse time | `AsciiParser.parseBuffer` / `parse` | the byte count is the write, not the message — a segment may hold several or half of one |
84
+ | messages decoded, by msgType | `AsciiParser.msg` | bounded label set, ~30 values |
85
+ | parse buffer high water | `ElasticBuffer.currentSize` vs `returnTo` | a buffer that keeps growing past `returnTo` is the early warning for a slow consumer |
86
+ | messages encoded, bytes out | `MsgTransmitter.send` / `settle` | `settle` already carries the payload and any error |
87
+ | session state transitions | `FixSession.setState` | transition counts, and time spent in each state |
88
+ | application vs session traffic | `AsciiSession.dispatchMsg` | the split matters: heartbeats are not throughput |
89
+ | gaps, resends, logon retries | `SessionSequenceCoordinator` | `pendingResendRequests`, `logonRetryCount` are already tracked |
90
+ | store depth, put latency | `FixMsgMemoryStore` / `FileSessionStore` | file store latency is the one that will surprise people |
91
+ | transports accepted, harvested | `TcpAcceptor` | already emits `transport` and `harvested` |
92
+ | reconnects, recovery attempts | `RecoveringTcpInitiator` | see `recovery policy` logging added in 5.11.0 |
93
+
94
+ **Bytes per second through the parser is the headline number** and deserves care: it is
95
+ what an evaluator asks for, and it is the one most easily measured wrongly. Count bytes
96
+ where they enter the parser, not where they leave the socket, and keep decode time
97
+ separate from `toObject()` — the benchmarking doc already draws that line as
98
+ `parse:view` against `parse:object`, and live instrumentation should draw it in the same
99
+ place.
100
+
101
+ ## The metric set
102
+
103
+ Names in Prometheus form; OpenTelemetry and the C# port use the same words. Prefix is
104
+ `fix_` rather than `jspurefix_` so both engines populate one dashboard.
105
+
106
+ | metric | type | labels |
107
+ | --- | --- | --- |
108
+ | `fix_session_up` | gauge (0/1) | `session`, `role` |
109
+ | `fix_session_state` | gauge (enum value) | `session` |
110
+ | `fix_messages_total` | counter | `session`, `direction`, `msg_type`, `class` (admin/app) |
111
+ | `fix_bytes_total` | counter | `session`, `direction` |
112
+ | `fix_parse_duration_seconds` | histogram | — |
113
+ | `fix_encode_duration_seconds` | histogram | — |
114
+ | `fix_seq_num` | gauge | `session`, `direction` |
115
+ | `fix_resend_requests_total` | counter | `session`, `direction` |
116
+ | `fix_gaps_detected_total` | counter | `session` |
117
+ | `fix_reconnects_total` | counter | `session` |
118
+ | `fix_store_depth` | gauge | `session` |
119
+ | `fix_transports_open` | gauge | `listener` |
120
+
121
+ Alerting mostly falls out of these: `fix_session_up == 0` during trading hours,
122
+ `rate(fix_gaps_detected_total[5m]) > 0`, a seq num that stops advancing, a store depth
123
+ that only grows.
124
+
125
+ ### Cardinality
126
+
127
+ A wildcard acceptor (`TargetCompID: '*'`) can meet an unbounded set of counterparties, so
128
+ **`session` is not automatically a safe label**. A venue with churny clients would grow
129
+ the series set without limit. Options: cap the labelled set and bucket the rest as
130
+ `other`, make per-session labelling opt-in, or expose per-session detail only through the
131
+ diagnostics endpoint and keep the scrape aggregated. Decide before shipping, not after
132
+ someone's Prometheus falls over.
133
+
134
+ `msg_type` is safe — bounded by the dictionary.
135
+
136
+ ## Structured logging
137
+
138
+ **This lands first, on its own.** Not because it is smaller — because it delivers the
139
+ whole Elastic story by itself, and because the pass that adds fields is the pass that
140
+ locates every future counter site.
141
+
142
+ Worth separating two consumers that want opposite things:
143
+
144
+ - **Filebeat and Elastic are push.** The engine writes JSON lines, Filebeat harvests,
145
+ Kibana does the drill-down. No server in the engine at all.
146
+ - **A live diagnostics endpoint is pull.** For when there is no Elastic stack, or when
147
+ someone wants the state of a process now rather than after a harvest cycle.
148
+
149
+ The click-through-and-drill-down picture is entirely the first of those. That makes the
150
+ web server in [packaging](#packaging-boundary) an independent, later, optional thing
151
+ rather than a prerequisite.
152
+
153
+ ### The leverage is bound context, not the call sites
154
+
155
+ The engine has **307 logger call sites** (239 `info`, 39 `warning`, 15 `error`, 14 `debug`)
156
+ but only **29 places a logger is constructed**. And the identity is already at those 29:
157
+
158
+ ```ts
159
+ this.sessionLogger = config.logFactory.logger(`${this.me}:${peerCompId}:FixSession`)
160
+ ```
161
+
162
+ That is the session identity, built once, and completely opaque downstream — the "regex
163
+ over English" problem, except it is not even English, it is punctuation. So the first move
164
+ is not widening 307 call sites, it is widening the 29. Bound context makes every one of the
165
+ 307 filterable without touching any of them.
166
+
167
+ Per-call fields then matter only where a line carries a *value* worth aggregating —
168
+ `msgType`, seq nums, byte counts, state transitions, gap ranges. Perhaps 25 lines, chosen
169
+ deliberately. They are the same chokepoints [layer 1](#layer-1--chokepoints) lists, which
170
+ is why doing this first leaves the metrics work half-located.
171
+
172
+ ### The interface
173
+
174
+ ```ts
175
+ export type JsFixLogFields = Record<string, unknown>
176
+
177
+ export interface IJsFixLogger {
178
+ info: (message: string, fields?: JsFixLogFields) => void
179
+ warning: (message: string, fields?: JsFixLogFields) => void
180
+ debug: (message: string, fields?: JsFixLogFields) => void
181
+ error: (e: Error, fields?: JsFixLogFields) => void
182
+ }
183
+
184
+ export abstract class JsFixLoggerFactory {
185
+ public abstract logger (type: string, context?: JsFixLogFields): IJsFixLogger
186
+ }
187
+ ```
188
+
189
+ Non-breaking, and worth being precise about why: TypeScript lets an implementation declare
190
+ *fewer* parameters than the interface, so a client's existing `info(message: string)` still
191
+ satisfies it, and their `JsFixLoggerFactory` subclass keeps working while ignoring the new
192
+ argument. Callers are unaffected either way.
193
+
194
+ `error` gains fields as well as the others. It currently takes only an `Error` — no
195
+ message, no context — and those 15 sites are the ones most wanted at 3am.
196
+
197
+ **Bound context, not a `child()` logger.** It fits the 29 construction sites, and it ports:
198
+ a `child()` would put an object lifecycle in the contract. See
199
+ [porting](#porting-to-cspurefix).
200
+
201
+ ### Not breaking the existing line
202
+
203
+ The engine has been live for years; people have grep and scrapers built on the current
204
+ output. That output must not move.
205
+
206
+ ```
207
+ 2026-08-16T14:14:56.102Z [skeleton_server:skeleton-client:FixSession] info: peer sent ...
208
+ ```
209
+
210
+ The shipped format names exactly four fields:
211
+
212
+ ```ts
213
+ printf(info => `${info.timestamp} [${info.type}] ${info.level}: ${info.message}`)
214
+ ```
215
+
216
+ So as long as context and per-call fields go into the winston info object as **separate
217
+ keys**, that printf never sees them. Verified: the same call with and without the new keys
218
+ renders byte-identically apart from the millisecond.
219
+
220
+ Three rules follow, and each has bitten already:
221
+
222
+ 1. **`type` stays verbatim at all 29 sites.** It cannot be composed from the fields — the
223
+ sites have no single rule between them (bare literals `'acceptor'`, one-part
224
+ `` `${this.me}` ``, two-part `` `${name}:TcpInitiator` ``, three-part
225
+ `` `${me}:${peer}:FixSession` ``, and one dotted `` `${type}.${t}` ``). Deriving it
226
+ means per-site rules, which is the same edit with more ways to get a string subtly
227
+ wrong. The resulting duplication between `type` and the fields is the price of not
228
+ breaking grep, and it is cheap. In a JSON rendering `type` is simply dropped, because
229
+ `component` and `app` carry it properly.
230
+ 2. **Fields are namespaced, never spread.** They travel under `context` and `fields` keys
231
+ and the *formatter* decides how to flatten. Spreading at the top level lets a caller's
232
+ `{ message: … }` clobber the real message. It also keeps the contract
233
+ transport-agnostic, which is what makes it port.
234
+ 3. **A winston format function must mutate `info`, not return a new object.** Returning a
235
+ fresh object drops `Symbol.for('level')` and winston discards the record silently — no
236
+ output, no error. Found the hard way while proving the above.
237
+
238
+ ### The default does not change
239
+
240
+ `WinstonLogger.consoleOptions()` does **not** emit JSON today — it is a `printf` rendering
241
+ prose, so "winston gives JSON for free" is not true as things stand. JSON arrives as a new
242
+ opt-in `ecsOptions()`, and the console default stays exactly as it is. Not only for this
243
+ release: changing a default log format is a major-version event at best and probably never
244
+ worth it.
245
+
246
+ `WinstonLogger.plain()` is message-only and used for FIX wire logs. That is a replay
247
+ artifact, not structured-log material, and stays as it is.
248
+
249
+ Two things make "same output after upgrade" a guarantee rather than a promise:
250
+
251
+ - **A golden-output test** rendering lines through `consoleOptions()` with and without
252
+ context and fields, asserting the strings are identical. Anyone later improving
253
+ `appFormat` then fails a test instead of breaking a customer's scraper.
254
+ - **The fields commit rewords no messages.** Mixing the two makes a before/after log diff
255
+ unreadable. Keep them separate so the diff at a given level is genuinely empty.
256
+
257
+ ### Field names
258
+
259
+ ECS names where they exist — `service.name`, `log.level`, `error.message`,
260
+ `error.stack_trace` — and everything engine-specific under `fix.*`. Context keys are plain
261
+ words; the formatter applies the prefix.
262
+
263
+ | context key | rendered | meaning |
264
+ | --- | --- | --- |
265
+ | `component` | `fix.component` | the class, e.g. `FixSession`, `TcpAcceptor` |
266
+ | `app` | `fix.app` | application name from the session description |
267
+ | `peer` | `fix.peer` | counterparty comp id, once known |
268
+ | `role` | `fix.role` | `initiator` or `acceptor` |
269
+
270
+ This is the metric prefix decision applied to logs: `fix.msg_type` in a log line,
271
+ `fix_messages_total{msg_type}` in a metric — same words, each in its own convention's
272
+ punctuation, so one dashboard serves both engines.
273
+
274
+ `fix.session` in the canonical `BeginString-Sender-Target` form is deliberately **not** in
275
+ the table above. A wildcard acceptor does not know its peer until bind time, so the field
276
+ would be absent or wrong on exactly the sessions that need it most. It arrives with the
277
+ metrics work, where `SessionId` is properly to hand — `AsciiSession` already rebuilds its
278
+ logger at bind for this reason, so the seam is there.
279
+
280
+ Rendered, one call site gives either:
281
+
282
+ ```
283
+ 2026-08-16T14:17:31.864Z [skeleton_server:skeleton-client:FixSession] info: peer sent ResetSeqNumFlag=Y with seqNum=1, weAlsoReset=true
284
+ ```
285
+
286
+ ```json
287
+ {"@timestamp":"2026-08-16T14:17:59.278Z","log.level":"info","service.name":"jspurefix",
288
+ "message":"peer sent ResetSeqNumFlag=Y with seqNum=1, weAlsoReset=true",
289
+ "fix.component":"FixSession","fix.app":"skeleton_server","fix.peer":"skeleton-client",
290
+ "fix.reset_seq_num_flag":true,"fix.seq_num":1,"fix.we_also_reset":true}
291
+ ```
292
+
293
+ ## Packaging boundary
294
+
295
+ `express` and `axios` are **already** in `dependencies`, which is the unfinished half of
296
+ [#91](https://github.com/TimelordUK/jspurefix/issues/91): every consumer installs them
297
+ whether or not they are used. A diagnostics web server is exactly how that happens a
298
+ second time, and the fact that express is already present is what will make it tempting.
299
+
300
+ Proposed:
301
+
302
+ - **core** — snapshot, sink interface, counters. No new dependencies at all.
303
+ - **exporters** — Prometheus text format is about fifty lines and needs nothing;
304
+ OpenTelemetry needs `@opentelemetry/api` as an **optional peer** dependency.
305
+ - **diagnostics endpoint** — a separate package, or optional peer. It should also be off
306
+ by default and bind to loopback unless told otherwise: an unauthenticated page
307
+ describing your counterparties and sequence numbers is not something to open by
308
+ accident.
309
+
310
+ ## Porting to cspurefix
311
+
312
+ The contract above is deliberately language-neutral. Points where the runtimes genuinely
313
+ differ, and the spec should not pretend otherwise:
314
+
315
+ - **GC and heap.** V8 exposes collections through `perf_hooks`; the CLR has its own
316
+ counters. Do not invent a common schema — report each natively and let the dashboard
317
+ hold two panels.
318
+ - **Sink interface.** A plain interface with a no-op implementation in both; no
319
+ `EventEmitter` in the contract, since that would not port.
320
+ - **Logging shape.** .NET expresses this differently: bound context is `BeginScope`, and
321
+ per-call fields come from message templates (`"session {SessionId} up"`) rather than a
322
+ separate object. Both can emit identical JSON, but they are not the same API. The
323
+ portable contract is *bound fields plus per-call fields*, and each engine renders it
324
+ idiomatically — forcing the TypeScript shape onto `Microsoft.Extensions.Logging` would
325
+ produce something unidiomatic that nobody on that side would want to use. What must
326
+ match is the emitted field names, not the call signature.
327
+ - **Histograms.** Prometheus histograms need fixed buckets. Agree the bucket boundaries
328
+ here so latency panels are comparable between engines rather than merely similar.
329
+
330
+ ## Decisions still open
331
+
332
+ 1. Per-session snapshot with registry rollup, or one flat structure?
333
+ 2. Is `session` a metric label, and if so how is cardinality bounded?
334
+ 3. ~~Does structured logging land first, on its own?~~ **Yes** — see
335
+ [structured logging](#structured-logging). Bound context on the factory rather than a
336
+ `child()` logger, `type` kept verbatim, console format unchanged.
337
+ 4. Histogram buckets for parse and encode duration.
338
+ 5. Does the diagnostics endpoint live in this repository at all, or its own package? It
339
+ needs no framework — `node:http` and about a hundred lines — which is what stops
340
+ `express` being reached for a second time.
341
+ 6. Sampling: are parse timings measured on every message, or 1-in-N once throughput is
342
+ high enough for the measurement itself to show up in the measurement?
343
+ 7. Does the engine own the ECS field names, or hand people a formatter to remap? Someone
344
+ with an existing Splunk convention will want the latter.
345
+ 8. When per-call fields land, do the ~25 chosen lines also get their message text left
346
+ alone permanently, so the prose and the fields never drift apart?