Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: RedwoodJS Studio & OpenTelemetry (Dev) #7829

Merged
merged 94 commits into from
Apr 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
8ac177f
require a project js file to setup sdk
Josh-Walker-GM Feb 10, 2023
ca08d0c
check opentelemetry setup script exists before requiring
Josh-Walker-GM Feb 13, 2023
3eb168f
initial setup command
Josh-Walker-GM Feb 13, 2023
65ba6b9
refactoring and adding envelop plugin option
Josh-Walker-GM Feb 14, 2023
72e3c2f
include prisma and fastify by default
Josh-Walker-GM Feb 15, 2023
0e71e02
experimental service wrapper
Josh-Walker-GM Feb 17, 2023
aa471a3
initial babel plugin for wrapping
Josh-Walker-GM Feb 18, 2023
942c164
more wip
Josh-Walker-GM Mar 1, 2023
cd6a0a8
minor fixes
Josh-Walker-GM Mar 3, 2023
a2d213e
some restructuring
Josh-Walker-GM Mar 4, 2023
482c1e5
mvp get config options
Josh-Walker-GM Mar 7, 2023
d6cd4d7
Added in memory or file based database and toml config option
Josh-Walker-GM Mar 7, 2023
f882ffc
Ads prisma query view and graphiql
dthyresson Mar 7, 2023
9e3a861
Adds logo
dthyresson Mar 7, 2023
7741c85
DISTINCT THE VIEW
dthyresson Mar 7, 2023
d8f3913
BE to get headers and config
dthyresson Mar 8, 2023
560d39e
Create dbAuth headers fro dashboard
dthyresson Mar 8, 2023
fb253c9
fix for babel plugin async issue
Josh-Walker-GM Mar 8, 2023
3172a77
make resolver spans children on the graphql span
Josh-Walker-GM Mar 8, 2023
b742cb8
Set dbAuth cookie headers from envar
dthyresson Mar 8, 2023
c002d5d
Config updates. get gql endpoint
dthyresson Mar 9, 2023
f0d1df1
adds supabase and netlify
dthyresson Mar 10, 2023
6ce7b8b
UI update
Josh-Walker-GM Mar 11, 2023
b2f7f23
Merge remote-tracking branch 'origin/jgmw-experiment-otel-framework' …
Josh-Walker-GM Mar 11, 2023
836731a
Get userId and email from toml to set headers
dthyresson Mar 11, 2023
a0981e6
SQL page and live data on overview page
Josh-Walker-GM Mar 11, 2023
6e91970
Fixes dbAuth uid session int
dthyresson Mar 11, 2023
792633a
remaining mvp placeholder implemented
Josh-Walker-GM Mar 11, 2023
cf1cae7
Merge remote-tracking branch 'origin/jgmw-experiment-otel-framework' …
Josh-Walker-GM Mar 11, 2023
7f45407
fix dist-frontend
Josh-Walker-GM Mar 11, 2023
f8b3bb2
Skeleton of the config page
Josh-Walker-GM Mar 11, 2023
24c8e38
Clean up config settings
dthyresson Mar 12, 2023
b97d863
UI tweaks
dthyresson Mar 12, 2023
78fb9cd
Minor ui tweaks
dthyresson Mar 12, 2023
3733c83
switch to studio over dashboard
Josh-Walker-GM Mar 14, 2023
a988e73
Switch to simple span processor by default
Josh-Walker-GM Mar 14, 2023
9f90ac1
Rework color scheme
dthyresson Mar 15, 2023
381c65c
UI tweaks
dthyresson Mar 15, 2023
ec672a3
UI
dthyresson Mar 15, 2023
63a0c54
update studio deps and merge main
Josh-Walker-GM Mar 15, 2023
7b6a95d
fix dev deps
Josh-Walker-GM Mar 15, 2023
e55bd77
Merge branch 'main' into jgmw-experiment-otel-framework
jtoar Mar 16, 2023
a9135b8
Added graphql proxy to avoid cors setup
Josh-Walker-GM Mar 16, 2023
96615db
Merge remote-tracking branch 'origin/jgmw-experiment-otel-framework' …
Josh-Walker-GM Mar 16, 2023
1b851b0
Ignore studio dist-* in linting
Josh-Walker-GM Mar 16, 2023
e9033ad
Adds graphql span counts
dthyresson Mar 16, 2023
bbba2a1
Merge remote-tracking branch 'origin/jgmw-experiment-otel-framework' …
Josh-Walker-GM Mar 16, 2023
e076340
Update dist-frontend
Josh-Walker-GM Mar 16, 2023
277bd85
update snapshot for config updates
Josh-Walker-GM Mar 16, 2023
7abe526
handle null context
Josh-Walker-GM Mar 16, 2023
f175d7a
CountCard component for landing page
Josh-Walker-GM Mar 16, 2023
735c4e6
Component for enhancement features list
Josh-Walker-GM Mar 16, 2023
38a82d4
Fix colors due to Tailwind needs
dthyresson Mar 17, 2023
7b20b93
Add enable flag, rename api sdk file config, conditionally enable bab…
Josh-Walker-GM Mar 20, 2023
769a37b
Merge branch 'main' into jgmw-experiment-otel-framework
Josh-Walker-GM Mar 20, 2023
d335f47
Fix deps and async everything - to be discussed/reverted!
Josh-Walker-GM Mar 20, 2023
e556080
Merge remote-tracking branch 'origin/main' into jgmw-experiment-otel-…
Josh-Walker-GM Mar 27, 2023
9859260
use a function to wrap with otel
Josh-Walker-GM Mar 27, 2023
becb0e9
Remove unneeded comments
Josh-Walker-GM Mar 27, 2023
d206589
Rework studio config
dthyresson Mar 27, 2023
8320cb4
Configure graphqlEndpoint
dthyresson Mar 27, 2023
f543318
Update config
dthyresson Mar 28, 2023
86b030c
Merge branch 'main' into jgmw-experiment-otel-framework
Josh-Walker-GM Mar 28, 2023
dffa201
fix deps, fix test with config mock
Josh-Walker-GM Mar 28, 2023
1258eec
Merge branch 'main' into jgmw-experiment-otel-framework
Josh-Walker-GM Mar 29, 2023
04f8c4a
Remove unneeded template file
Josh-Walker-GM Mar 29, 2023
86dbfe8
Update the readme
Josh-Walker-GM Mar 29, 2023
a6076fb
add running instruction
Josh-Walker-GM Mar 29, 2023
60ff494
fix deps
Josh-Walker-GM Mar 29, 2023
b637f8e
fix main entry
Josh-Walker-GM Apr 3, 2023
7bf4ae7
check netlify secret
Josh-Walker-GM Apr 3, 2023
6627eb7
Merge remote-tracking branch 'origin/main' into jgmw-experiment-otel-…
Josh-Walker-GM Apr 3, 2023
e449c65
fix deps
Josh-Walker-GM Apr 3, 2023
03d7cd8
Merge branch 'main' into jgmw-experiment-otel-framework
Josh-Walker-GM Apr 4, 2023
5f69d04
Add experimental prefix to config
Josh-Walker-GM Apr 5, 2023
65ac915
Update readme
Josh-Walker-GM Apr 5, 2023
5e4091e
Move to being explicit about experimental commands
Josh-Walker-GM Apr 5, 2023
3e6fc9e
Fix missing experimental prefix with getConfig
Josh-Walker-GM Apr 5, 2023
cd045ed
Add alias 'exp' for 'experimental' command
Josh-Walker-GM Apr 5, 2023
79fe5ac
fix config mock in graphql-server test
Josh-Walker-GM Apr 5, 2023
cd8b934
Merge remote-tracking branch 'origin/jgmw-experiment-otel-framework' …
Josh-Walker-GM Apr 5, 2023
c946476
Merge remote-tracking branch 'origin/main' into jgmw-experiment-otel-…
Josh-Walker-GM Apr 5, 2023
01d9d8b
fix deps
Josh-Walker-GM Apr 5, 2023
c8930d7
Add check to import within otel babel plugin
Josh-Walker-GM Apr 5, 2023
d2106d8
Merge branch 'main' into jgmw-experiment-otel-framework
Josh-Walker-GM Apr 6, 2023
ed8bf41
Merge remote-tracking branch 'origin/main' into jgmw-experiment-otel-…
Josh-Walker-GM Apr 10, 2023
4b3f161
update scripts to dist-backend
Josh-Walker-GM Apr 11, 2023
1ad0a4f
rename remaining 'dashboard' to 'studio'
Josh-Walker-GM Apr 11, 2023
f4b0151
Add link to forum page in sidebar
Josh-Walker-GM Apr 11, 2023
cca3f4f
README updates
dthyresson Apr 12, 2023
6acaa02
README typo fixes
dthyresson Apr 12, 2023
60f8411
More future ideas
dthyresson Apr 12, 2023
9e708ea
typo
dthyresson Apr 13, 2023
d68fa93
Merge branch 'main' into jgmw-experiment-otel-framework
Josh-Walker-GM Apr 15, 2023
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
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ module.exports = {
'packages/core/**/__fixtures__/**/*',
'packages/codemods/**/__testfixtures__/**/*',
'packages/core/config/storybook/**/*',
'packages/studio/dist-*/**/*',
],
rules: {
'@typescript-eslint/no-explicit-any': 'off',
Expand Down
22 changes: 22 additions & 0 deletions packages/api-server/src/watch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { fork } from 'child_process'
import type { ChildProcess } from 'child_process'
import fs from 'fs'
import path from 'path'

import c from 'ansi-colors'
Expand Down Expand Up @@ -74,6 +75,27 @@ const rebuildApiServer = async () => {
const forkOpts = {
execArgv: process.execArgv,
}

// OpenTelemetry SDK Setup
if (getConfig().experimental.opentelemetry.enabled) {
const opentelemetrySDKScriptPath =
getConfig().experimental.opentelemetry.apiSdk
if (opentelemetrySDKScriptPath) {
console.log(
`Setting up OpenTelemetry using the setup file: ${opentelemetrySDKScriptPath}`
)
if (fs.existsSync(opentelemetrySDKScriptPath)) {
forkOpts.execArgv = forkOpts.execArgv.concat([
`--require=${opentelemetrySDKScriptPath}`,
])
} else {
console.error(
`OpenTelemetry setup file does not exist at ${opentelemetrySDKScriptPath}`
)
}
}
}

const debugPort = argv['debug-port']
if (debugPort) {
forkOpts.execArgv = forkOpts.execArgv.concat([`--inspect=${debugPort}`])
Expand Down
1 change: 1 addition & 0 deletions packages/auth-providers/dbAuth/api/src/decoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const authDecoder: Decoder = async (
const authHeaderUserId = authHeaderValue

if (session.id.toString() !== authHeaderUserId) {
console.error('Authorization header does not match decrypted user ID')
throw new Error('Authorization header does not match decrypted user ID')
}

Expand Down
29 changes: 29 additions & 0 deletions packages/cli/src/commands/experimental.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import terminalLink from 'terminal-link'

import detectRwVersion from '../middleware/detectProjectRwVersion'

export const command = 'experimental <command>'
export const aliases = ['exp']
export const description = 'Run or setup experimental features'

export const builder = (yargs) =>
yargs
.commandDir('./experimental', {
recurse: true,
// @NOTE This regex will ignore all commands nested more than two
// levels deep.
// e.g. /setup/hi.js & setup/hi/hi.js are picked up, but
// setup/hi/hello/bazinga.js will be ignored
// The [/\\] bit is for supporting both windows and unix style paths
// Also take care to not trip up on paths that have "setup" earlier
// in the path by eagerly matching in the start of the regexp
exclude: /.*[/\\]experimental[/\\].*[/\\].*[/\\]/,
})
.demandCommand()
.middleware(detectRwVersion)
.epilogue(
`Also see the ${terminalLink(
'Redwood CLI Reference',
'https://redwoodjs.com/docs/cli-commands#experimental'
)}`
)
28 changes: 28 additions & 0 deletions packages/cli/src/commands/experimental/setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import terminalLink from 'terminal-link'

import detectRwVersion from '../../middleware/detectProjectRwVersion'

export const command = 'setup <command>'
export const description = 'Setup experimental features'

export const builder = (yargs) =>
yargs
.commandDir('./setup', {
recurse: true,
// @NOTE This regex will ignore all commands nested more than two
// levels deep.
// e.g. /setup/hi.js & setup/hi/hi.js are picked up, but
// setup/hi/hello/bazinga.js will be ignored
// The [/\\] bit is for supporting both windows and unix style paths
// Also take care to not trip up on paths that have "setup" earlier
// in the path by eagerly matching in the start of the regexp
exclude: /.*[/\\]setup[/\\].*[/\\].*[/\\]/,
})
.demandCommand()
.middleware(detectRwVersion)
.epilogue(
`Also see the ${terminalLink(
'Redwood CLI Reference',
'https://redwoodjs.com/docs/cli-commands#experimental'
)}`
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export const command = 'opentelemetry'

export const description = 'Setup OpenTelemetry within the api side'

export const builder = (yargs) => {
yargs.option('force', {
alias: 'f',
default: false,
description: 'Overwrite existing configuration',
type: 'boolean',
})
yargs.option('verbose', {
alias: 'v',
default: false,
description: 'Print more logs',
type: 'boolean',
})
}

export const handler = async (options) => {
const { handler } = await import('./opentelemetryHandler')
return handler(options)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import fs from 'fs'
import path from 'path'

import chalk from 'chalk'
import execa from 'execa'
import { Listr } from 'listr2'

import { addApiPackages } from '@redwoodjs/cli-helpers'
import { getConfigPath } from '@redwoodjs/project-config'
import { errorTelemetry } from '@redwoodjs/telemetry'

import { getPaths, transformTSToJS, writeFile } from '../../../../lib'
import c from '../../../../lib/colors'
import { isTypeScriptProject } from '../../../../lib/project'

export const handler = async ({ force, verbose }) => {
const ts = isTypeScriptProject()

// Used in multiple tasks
const opentelemetryScriptPath = `${getPaths().api.src}/opentelemetry.${
ts ? 'ts' : 'js'
}`

// TODO: Consider extracting these from the templates? Consider version pinning?
const opentelemetryPackages = [
'@opentelemetry/api',
'@opentelemetry/instrumentation',
'@opentelemetry/exporter-trace-otlp-http',
'@opentelemetry/resources',
'@opentelemetry/sdk-node',
'@opentelemetry/semantic-conventions',
'@opentelemetry/instrumentation-http',
'@opentelemetry/instrumentation-fastify',
'@prisma/instrumentation',
]

const opentelemetryTasks = [
{
title: `Adding OpenTelemetry setup files...`,
task: () => {
const setupTemplateContent = fs.readFileSync(
path.resolve(__dirname, 'templates', 'opentelemetry.ts.template'),
'utf-8'
)
const setupScriptContent = ts
? setupTemplateContent
: transformTSToJS(opentelemetryScriptPath, setupTemplateContent)

return [
writeFile(opentelemetryScriptPath, setupScriptContent, {
overwriteExisting: force,
}),
]
},
},
{
title: 'Adding config to redwood.toml...',
task: (_ctx, task) => {
const redwoodTomlPath = getConfigPath()
const configContent = fs.readFileSync(redwoodTomlPath, 'utf-8')
if (!configContent.includes('[experimental.opentelemetry]')) {
// Use string replace to preserve comments and formatting
writeFile(
redwoodTomlPath,
configContent.concat(
`\n[experimental.opentelemetry]\n\tenabled = true\n\tapiSdk = "${opentelemetryScriptPath}"`
),
{
overwriteExisting: true, // redwood.toml always exists
}
)
} else {
task.skip(
`The [experimental.opentelemetry] config block already exists in your 'redwood.toml' file.`
)
}
},
},
addApiPackages(opentelemetryPackages),
]

const prismaTasks = [
{
title: 'Setup Prisma OpenTelemetry...',
task: (_ctx, task) => {
const schemaPath = path.join(getPaths().api.db, 'schema.prisma') // TODO: schema file is already in getPaths()?
const schemaContent = fs.readFileSync(schemaPath, {
encoding: 'utf-8',
flag: 'r',
})

const clientConfig = schemaContent
.slice(
schemaContent.indexOf('generator client') +
'generator client'.length,
schemaContent.indexOf(
'}',
schemaContent.indexOf('generator client')
) + 1
)
.trim()

const previewLineExists = clientConfig.includes('previewFeatures')
let newSchemaContents = schemaContent
if (previewLineExists) {
task.skip(
'Please add "tracing" to your previewFeatures in prisma.schema'
)
} else {
const newClientConfig = clientConfig.trim().split('\n')
newClientConfig.splice(
newClientConfig.length - 1,
0,
'previewFeatures = ["tracing"]'
)
newSchemaContents = newSchemaContents.replace(
clientConfig,
newClientConfig.join('\n')
)
}

return writeFile(schemaPath, newSchemaContents, {
overwriteExisting: true, // We'll likely always already have this file in the project
})
},
},
{
title: 'Regenerate the Prisma client...',
task: (_ctx, _task) => {
return execa(`yarn rw prisma generate`, {
stdio: 'inherit',
shell: true,
cwd: getPaths().web.base,
})
},
},
]

const tasks = new Listr(
[
{
title: 'Confirmation',
task: async (_ctx, task) => {
const confirmation = await task.prompt({
type: 'Confirm',
message: 'OpenTelemetry support is experimental. Continue?',
})

if (!confirmation) {
throw new Error('User aborted')
}
},
},
...opentelemetryTasks,
...prismaTasks,
{
title: 'One more thing...',
task: (_ctx, task) => {
console.log(
`${chalk.hex('#ff845e')(
`------------------------------------------------------------------\n 🧪 ${chalk.green(
'Experimental Feature'
)} 🧪\n------------------------------------------------------------------`
)}`
)
console.log(
`Studio is an experimental feature, please find documentation and links to provide feedback at:\n -> https://community.redwoodjs.com/t/redwood-studio-experimental/4771`
)
console.log(
`${chalk.hex('#ff845e')(
'------------------------------------------------------------------'
)}\n`
)

task.title = `One more thing...\n
${c.green('OpenTelemetry Support is still experimental!')}
${c.green(
'Please let us know if you find bugs or quirks or if you have any feedback!'
)}
${chalk.hex('#e8e8e8')(
'https://community.redwoodjs.com/t/opentelemetry-support-experimental/4772'
)}
`
},
},
],
{
rendererOptions: { collapse: false, persistentOutput: true },
renderer: verbose ? 'verbose' : 'default',
}
)

try {
await tasks.run()
} catch (e) {
errorTelemetry(process.argv, e.message)
console.error(c.error(e.message))
process.exit(e?.exitCode || 1)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
const { diag, DiagConsoleLogger, DiagLogLevel } = require('@opentelemetry/api')
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http')
const { registerInstrumentations } = require('@opentelemetry/instrumentation')
const {
FastifyInstrumentation,
} = require('@opentelemetry/instrumentation-fastify')
const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http')
const { Resource } = require('@opentelemetry/resources')
const {
NodeTracerProvider,
SimpleSpanProcessor,
} = require('@opentelemetry/sdk-trace-node')
const {
SemanticResourceAttributes,
} = require('@opentelemetry/semantic-conventions')
const { PrismaInstrumentation } = require ('@prisma/instrumentation')

// You may wish to set this to DiagLogLevel.DEBUG when you need to debug opentelemetry itself
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.INFO)

const resource = Resource.default().merge(
new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: 'redwood-app',
[SemanticResourceAttributes.SERVICE_VERSION]: '0.0.0',
})
)

const exporter = new OTLPTraceExporter({
// Update this URL to point to where your OTLP compatible collector is listening
// The redwood development studio (`yarn rw exp studio`) can collect your telemetry at `http://127.0.0.1:4318/v1/traces`
url: 'http://127.0.0.1:4318/v1/traces',
})

// You may wish to switch to BatchSpanProcessor in production as it is the recommended choice for performance reasons
const processor = new SimpleSpanProcessor(exporter)

const provider = new NodeTracerProvider({
resource: resource,
})
provider.addSpanProcessor(processor)

// Optionally register instrumentation libraries here
registerInstrumentations({
tracerProvider: provider,
instrumentations: [
new HttpInstrumentation(),
new FastifyInstrumentation(),
new PrismaInstrumentation({
middleware: true,
})
],
})

provider.register()
Loading