Files
kestrelos/server/utils/logger.js
Keli Grubb e61e6bc7e3
All checks were successful
ci/woodpecker/push/push Pipeline was successful
major: kestrel is now a tak server (#6)
## Added

- CoT (Cursor on Target) server on port 8089 enabling ATAK/iTAK device connectivity
- Support for TAK stream protocol and traditional XML CoT messages
- TLS/SSL support with automatic fallback to plain TCP
- Username/password authentication for CoT connections
- Real-time device position tracking with TTL-based expiration (90s default)
- API endpoints: `/api/cot/config`, `/api/cot/server-package`, `/api/cot/truststore`, `/api/me/cot-password`
- TAK Server section in Settings with QR code for iTAK setup
- ATAK password management in Account page for OIDC users
- CoT device markers on map showing real-time positions
- Comprehensive documentation in `docs/` directory
- Environment variables: `COT_PORT`, `COT_TTL_MS`, `COT_REQUIRE_AUTH`, `COT_SSL_CERT`, `COT_SSL_KEY`, `COT_DEBUG`
- Dependencies: `fast-xml-parser`, `jszip`, `qrcode`

## Changed

- Authentication system supports CoT password management for OIDC users
- Database schema includes `cot_password_hash` field
- Test suite refactored to follow functional design principles

## Removed

- Consolidated utility modules: `authConfig.js`, `authSkipPaths.js`, `bootstrap.js`, `poiConstants.js`, `session.js`

## Security

- XML entity expansion protection in CoT parser
- Enhanced input validation and SQL injection prevention
- Authentication timeout to prevent hanging connections

## Breaking Changes

- Port 8089 must be exposed for CoT server. Update firewall rules and Docker/Kubernetes configurations.

## Migration Notes

- OIDC users must set ATAK password via Account settings before connecting
- Docker: expose port 8089 (`-p 8089:8089`)
- Kubernetes: update Helm values to expose port 8089

Co-authored-by: Madison Grubb <madison@elastiflow.com>
Reviewed-on: #6
2026-02-17 16:41:41 +00:00

85 lines
2.4 KiB
JavaScript

/**
* Structured logger with request context support.
* Uses AsyncLocalStorage to provide request-scoped context that's automatically isolated per async context.
*/
import { AsyncLocalStorage } from 'node:async_hooks'
const asyncLocalStorage = new AsyncLocalStorage()
/**
* Run a function with logger context. Context is automatically isolated per async execution.
* @param {string} reqId - Request ID
* @param {string|null} uId - User ID (optional)
* @param {Function} fn - Function to run with context
* @returns {Promise<any>} Result of the function
*/
export function runWithContext(reqId, uId, fn) {
return asyncLocalStorage.run({ requestId: reqId, userId: uId }, fn)
}
/**
* Set context for the current async context. Use runWithContext() instead for proper isolation.
* @deprecated Use runWithContext() instead for proper async context isolation
* @param {string} reqId - Request ID
* @param {string|null} uId - User ID (optional)
*/
export function setContext(reqId, uId = null) {
const store = asyncLocalStorage.getStore()
if (store) {
store.requestId = reqId
store.userId = uId
}
}
/**
* Clear context for the current async context.
* @deprecated Context is automatically cleared when async context ends. Use runWithContext() instead.
*/
export function clearContext() {
const store = asyncLocalStorage.getStore()
if (store) {
store.requestId = null
store.userId = null
}
}
function getContext() {
return asyncLocalStorage.getStore() || { requestId: null, userId: null }
}
function formatMessage(level, message, context = {}) {
const { requestId, userId } = getContext()
const timestamp = new Date().toISOString()
const ctx = {
timestamp,
level,
requestId,
...(userId && { userId }),
...context,
}
return `[${level.toUpperCase()}] ${JSON.stringify({ message, ...ctx })}`
}
export function info(message, context = {}) {
console.log(formatMessage('info', message, context))
}
export function error(message, context = {}) {
const ctx = { ...context }
if (context.error && context.error.stack) {
ctx.stack = context.error.stack
}
console.error(formatMessage('error', message, ctx))
}
export function warn(message, context = {}) {
console.warn(formatMessage('warn', message, context))
}
export function debug(message, context = {}) {
if (process.env.NODE_ENV === 'development') {
console.debug(formatMessage('debug', message, context))
}
}