Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions api/deno.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
{
"imports": {
"@std/fmt": "jsr:@std/fmt@^1.0.9",
"@std/http": "jsr:@std/http@^1.0.25"
"@std/http": "jsr:@std/http@^1.0.25",
"@std/streams": "jsr:@std/streams@^1.1.1"
},
"name": "@01edu/api",
"version": "0.2.7",
"version": "0.2.8",
"license": "MIT",
"exports": {
"./context": "./context.ts",
Expand All @@ -13,6 +14,7 @@
"./response": "./response.ts",
"./router": "./router.ts",
"./server": "./server.ts",
"./validator": "./validator.ts"
"./validator": "./validator.ts",
"./local_ipc_client": "./local_ipc_client.ts"
}
}
18 changes: 17 additions & 1 deletion api/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,19 @@ if (APP_ENV !== 'dev' && APP_ENV !== 'prod' && APP_ENV !== 'test') {
throw Error(`APP_ENV: "${APP_ENV}" must be "dev", "test" or "prod"`)
}

/**
* The port number the application should listen on, determined by the `PORT` environment variable.
* Defaults to '8080' if not set.
*
* @example
* ```ts
* import { PORT } from '@01edu/api/env';
*
* console.log(`Server will listen on port: ${PORT}`);
* ```
*/
export const PORT: string = ENV('PORT', '8080')

/**
* The git commit SHA of the current build, typically provided by a CI/CD system.
*
Expand All @@ -78,7 +91,10 @@ export const CI_COMMIT_SHA: string = ENV('CI_COMMIT_SHA', '')
* };
* ```
*/
export const DEVTOOL_REPORT_TOKEN: string = ENV('DEVTOOL_REPORT_TOKEN', '')
export const DEVTOOL_REPORT_TOKEN: string = ENV(
'DEVTOOL_REPORT_TOKEN',
`localhost:${PORT}`,
)
/**
* The URL for a developer tool service.
*
Expand Down
49 changes: 49 additions & 0 deletions api/local_ipc_client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { TextLineStream } from '@std/streams/text-line-stream'

export const defaultSocketPath: string = Deno.build.os === 'windows'
? '\\\\.\\pipe\\01-devtools'
: `${Deno.env.get('XDG_RUNTIME_DIR') || '/tmp'}/01-devtools/01-devtools.sock`

const encoder = new TextEncoder()

async function sendCommand(
socketPath: string,
command: string,
): Promise<Record<string, unknown> | null> {
try {
const conn = await Deno.connect({ transport: 'unix', path: socketPath })
await conn.write(encoder.encode(`${command}\n`))
const reader = conn.readable
.pipeThrough(new TextDecoderStream())
.pipeThrough(new TextLineStream())
.getReader()
const { value } = await reader.read()
reader.releaseLock()
conn.close()
return value ? JSON.parse(value) : null
} catch {
return null
}
}

export let devtoolsPort: number | null = null

export interface RegisterPayload {
projectId: string
name?: string
url: string
sqlEndpoint?: string | null
}

export async function register(
payload: RegisterPayload,
socketPath = defaultSocketPath,
): Promise<null | void> {
const res = await sendCommand(
socketPath,
`register/${JSON.stringify(payload)}`,
)
if (!res) return null

devtoolsPort = res.port as number
}
57 changes: 36 additions & 21 deletions api/log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
DEVTOOL_REPORT_TOKEN,
DEVTOOL_URL,
} from './env.ts'
import { devtoolsPort } from './local_ipc_client.ts'

// Types
type LogLevel = 'info' | 'error' | 'warn' | 'debug'
Expand Down Expand Up @@ -146,7 +147,7 @@ const bind = (log: LogFunction) =>
*/
export const logger = async ({
filters,
batchInterval = 5000,
batchInterval = APP_ENV === 'prod' ? 5000 : 500,
maxBatchSize = 50,
logUrl = DEVTOOL_URL,
logToken = DEVTOOL_REPORT_TOKEN,
Expand Down Expand Up @@ -176,6 +177,27 @@ export const logger = async ({
}
}

function forwardLogsToDevtool(
level: LogLevel,
event: string,
props?: Record<string, unknown>,
) {
const { trace, span } = getContext()
const logData = {
severity_number: levels[level].level,
trace_id: trace,
span_id: span,
event_name: event,
attributes: props,
timestamp: now() * 1000,
service_version: version,
service_instance_id: startTime.toString(),
}

logBatch.push(logData)
logBatch.length >= maxBatchSize && flushLogs()
}

// DEVTOOLS Batch Logic
async function flushLogs() {
if (logBatch.length === 0) return
Expand All @@ -184,7 +206,11 @@ export const logger = async ({
logBatch = []

try {
const response = await fetch(logUrl!, {
const logUrlWithPort = APP_ENV === 'dev' && devtoolsPort
? `http://localhost:${devtoolsPort}/api/logs`
: logUrl

const response = await fetch(logUrlWithPort, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Expand All @@ -206,8 +232,7 @@ export const logger = async ({
const rootDir =
import.meta.dirname?.slice(0, -'/lib'.length).replaceAll('\\', '/') || ''

const f = filters || new Set()
if (APP_ENV === 'prod') {
if (APP_ENV === 'prod' || APP_ENV === 'dev') {
// Initialize batch interval
const interval = setInterval(flushLogs, batchInterval)

Expand All @@ -220,24 +245,14 @@ export const logger = async ({

Deno.addSignalListener('SIGINT', cleanup)
Deno.addSignalListener('SIGTERM', cleanup)
}

const f = filters || new Set()
if (APP_ENV === 'prod') {
return bind((level, event, props) => {
if (f.has(event)) return
const { trace, span } = getContext()
const logData = {
severity_number: levels[level].level,
trace_id: trace,
span_id: span,
event_name: event,
attributes: props,
timestamp: now() * 1000,
service_version: version,
service_instance_id: startTime.toString(),
}
// Local logging
console.log(event, props)

logBatch.push(logData)
logBatch.length >= maxBatchSize && flushLogs()
forwardLogsToDevtool(level, event, props)
console[level](event, props)
})
}

Expand All @@ -252,6 +267,7 @@ export const logger = async ({
if (APP_ENV === 'dev') {
return bind((level, event, props) => {
if (f.has(event)) return
forwardLogsToDevtool(level, event, props)
let callChain = ''
for (const s of Error('').stack!.split('\n').slice(2).reverse()) {
if (!s.includes(rootDir)) continue
Expand All @@ -264,7 +280,6 @@ export const logger = async ({
))
callChain = callChain ? `${callChain}/${coloredName}` : coloredName
}

const ev = `${makePrettyTimestamp(level, event)} ${callChain}`.trim()
props ? console[level](ev, props) : console[level](ev)
})
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  if (f.has(event)) return
  forwardLogsToDevtool(level, event, props)
  let callChain = ''
  for (const s of Error('').stack!.split('\n').slice(2).reverse()) {
    if (!s.includes(rootDir)) continue
    const fnName = s.split(' ').at(-2)
    if (!fnName || fnName === 'async' || fnName === 'at') continue
    const coloredName = colored[fnName] ||
      (colored[fnName] = colors
        [Object.keys(colored).length % colors.length](
          fnName,
        ))
    callChain = callChain ? `${callChain}/${coloredName}` : coloredName
  }
  const ev = `${makePrettyTimestamp(level, event)} ${callChain}`.trim()
  props ? console[level](ev, props) : console[level](ev)
}

Expand Down