Skip to content

Commit

Permalink
Allow to enable observability (APM, transactions, spans) (#1578)
Browse files Browse the repository at this point in the history
  • Loading branch information
ggrossetie authored Jun 17, 2023
1 parent e418635 commit f3caeaf
Show file tree
Hide file tree
Showing 8 changed files with 816 additions and 107 deletions.
3 changes: 2 additions & 1 deletion excalidraw/src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// must be declared first
import { logger } from './logger.js'
import http from 'node:http'
import micro from 'micro'
import { logger } from './logger.js'
import Worker from './worker.js'
import Task from './task.js'
import { create } from './browser-instance.js'
Expand Down
669 changes: 599 additions & 70 deletions mermaid/package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions mermaid/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"url": "https://github.com/yuzutech/kroki.git"
},
"dependencies": {
"elastic-apm-node": "3.47.0",
"lodash": "4.17.21",
"micro": "10.0.1",
"pino": "8.14.1",
Expand Down
60 changes: 60 additions & 0 deletions mermaid/src/apm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import apm from 'elastic-apm-node'
import process from 'node:process'
import { logger } from './logger.js'

const serviceName = process.env.KROKI_ELASTIC_APM_SERVICE_NAME || 'kroki-mermaid'
const secretToken = process.env.KROKI_ELASTIC_APM_SECRET_TOKEN
const serverUrl = process.env.KROKI_ELASTIC_APM_SECRET_URL
const environment = process.env.KROKI_ELASTIC_APM_ENVIRONMENT || 'prod'

if (secretToken && serverUrl) {
try {
apm.start({
serviceName,
secretToken,
serverUrl,
environment
})
} catch (err) {
logger.error({ err }, 'Unable to start Elastic APM')
}
}

/**
* @param {string} name
* @param {string} type
* @param {apm.Labels} [labels]
* @param {SpanOptions} [opts]
* @returns {Span|undefined}
*/
export function startSpan (name, type, labels, opts) {
if (apm.isStarted()) {
const span = apm.startSpan(name, opts)
if (labels) {
span.addLabels(labels)
}
return span
}
}

/**
* @param {Span|undefined} span
*/
export function successfulSpan (span) {
if (span) {
span.setOutcome('success')
span.end()
}
}

/**
* @param span
* @param {Error} err
*/
export function failureSpan (span, err) {
if (span) {
apm.logger.error({ err })
span.setOutcome('failure')
span.end()
}
}
5 changes: 4 additions & 1 deletion mermaid/src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// must be declared first
import { logger } from './logger.js'
import './apm.js'

import http from 'node:http'
import micro from 'micro'
import { logger } from './logger.js'
import { SyntaxError, Worker } from './worker.js'
import Task from './task.js'
import { create } from './browser-instance.js'
Expand Down
179 changes: 147 additions & 32 deletions mermaid/src/worker.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { URL, fileURLToPath } from 'node:url'
import { fileURLToPath, URL } from 'node:url'
import path from 'node:path'
import puppeteer from 'puppeteer'
// eslint-disable-next-line
import puppeteer, { Page, HTTPResponse } from 'puppeteer'
import { logger } from './logger.js'
import { updateConfig } from './config.js'
import { failureSpan, startSpan, successfulSpan } from './apm.js'

const __dirname = fileURLToPath(new URL('.', import.meta.url))

Expand All @@ -19,43 +21,23 @@ export class Worker {
}

async convert (task, config) {
const browser = await puppeteer.connect({
browserWSEndpoint: this.browserWSEndpoint,
ignoreHTTPSErrors: true
})
const page = await browser.newPage()
const mermaidConfig = task.mermaidConfig
if (config !== null && config !== undefined && typeof config[Symbol.iterator] === 'function') {
updateConfig(mermaidConfig, config)
}
const browser = await this._connect()
const page = await newPage(browser)
try {
const mermaidConfig = task.mermaidConfig
if (config !== null && config !== undefined && typeof config[Symbol.iterator] === 'function') {
updateConfig(mermaidConfig, config)
}
await page.setViewport({ height: 800, width: 600 })
await page.goto(this.pageUrl)
// QUESTION: should we reuse the page for performance reason ?
const evalResult = await page.evaluate(async (definition, mermaidConfig) => {
window.mermaid.initialize(mermaidConfig)
try {
const { svg } = await window.mermaid.render('container', definition)
return { svg, error: null }
} catch (err) {
return { svg: null, error: err }
}
}, task.source, mermaidConfig)

await this._goto(page)
const evalResult = await this._eval(page, task, mermaidConfig)
if (evalResult && evalResult.error) {
throw new SyntaxError(evalResult.error)
}

if (task.isPng) {
await page.setContent(evalResult.svg)
const container = await page.$('#container')
return await container.screenshot({
type: 'png',
omitBackground: true
})
} else {
return evalResult.svg
return await toPNG(page, evalResult.svg)
}
return evalResult.svg
} finally {
try {
await page.close()
Expand All @@ -69,4 +51,137 @@ export class Worker {
}
}
}

/**
* @param {Page} page
* @param {Task} task
* @param {{[key: string]: string|{}}} mermaidConfig
* @returns {Promise<{svg: string|null, error: Error|null}>}
* @private
*/
async _eval (page, task, mermaidConfig) {
const span = startSpan(
'Evaluate Mermaid definition',
'puppeteer',
{
mermaidConfig: JSON.stringify(mermaidConfig)
}
)
try {
const result = await page.evaluate(async (definition, mermaidConfig) => {
window.mermaid.initialize(mermaidConfig)
try {
const { svg } = await window.mermaid.render('container', definition)
return { svg, error: null }
} catch (err) {
return { svg: null, error: err }
}
}, task.source, mermaidConfig)
successfulSpan(span)
return result
} catch (err) {
if (span) {
// add source to troubleshoot
span.setLabel('source', task.source)
}
failureSpan(span, err)
throw err
}
}

/**
* @param {Page} page
* @returns {Promise<HTTPResponse|null>}
* @private
*/
async _goto (page) {
const span = startSpan(
'Navigate to URL',
'puppeteer',
{
pageClosed: page.isClosed()
}
)
try {
const response = await page.goto(this.pageUrl)
successfulSpan(span)
return response
} catch (err) {
failureSpan(span, err)
throw err
}
}

/**
* @returns {Promise<Browser>}
* @private
*/
async _connect () {
const span = startSpan(
'Attach Puppeteer to an existing browser instance',
'puppeteer',
{
browserWSEndpoint: this.browserWSEndpoint
}
)
try {
const browser = await puppeteer.connect({
browserWSEndpoint: this.browserWSEndpoint,
ignoreHTTPSErrors: true
})
successfulSpan(span)
return browser
} catch (err) {
failureSpan(span, err)
throw err
}
}
}

/**
* @param {Browser} browser
* @returns {Promise<Page>}
*/
async function newPage (browser) {
const span = startSpan(
'Create a new Page in the browser',
'puppeteer',
{
browserConnected: browser.isConnected()
}
)
try {
const page = await browser.newPage()
successfulSpan(span)
return page
} catch (err) {
failureSpan(span, err)
throw err
}
}

/**
*
* @param {Page} page
* @param {string} svg
* @returns {Promise<string|Buffer>}
*/
async function toPNG (page, svg) {
const span = startSpan(
'Convert SVG to PNG using screenshot',
'puppeteer'
)
try {
await page.setContent(svg)
const container = await page.$('#container')
const result = await container.screenshot({
type: 'png',
omitBackground: true
})
successfulSpan(span)
return result
} catch (err) {
failureSpan(span, err)
throw err
}
}
3 changes: 1 addition & 2 deletions mermaid/test/convert-test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const invalidSyntaxTests = [

async function getBrowser () {
return puppeteer.launch({
headless: 'new',
args: [
'--disable-dev-shm-usage',
'--no-first-run',
Expand Down Expand Up @@ -78,8 +79,6 @@ describe('#convert', function () {
const worker = new Worker(browser)
const result = await worker.convert(new Task(testCase.content, true))

console.log({result})

const image = PNG.sync.read(result) // this will fail on invalid image

expect(image.width).to.be.closeTo(testCase.width, 20)
Expand Down
3 changes: 2 additions & 1 deletion mermaid/test/run.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
#!/usr/bin/env node
'use strict';

// must be declared first
import '../src/logger.js'
import ospath from 'node:path'
import { URL, fileURLToPath } from 'node:url'
import Mocha from 'mocha'
import '../src/logger.js'

const __dirname = fileURLToPath(new URL('.', import.meta.url))

Expand Down

0 comments on commit f3caeaf

Please sign in to comment.