@lowdefy/server 4.5.2 → 4.7.0

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 (39) hide show
  1. package/lib/build/app.js +19 -0
  2. package/lib/build/auth.js +19 -0
  3. package/lib/build/config.js +19 -0
  4. package/lib/build/logger.js +19 -0
  5. package/lib/client/Page.js +1 -1
  6. package/lib/client/auth/Auth.js +2 -2
  7. package/lib/client/auth/AuthConfigured.js +2 -2
  8. package/lib/client/auth/AuthNotConfigured.js +1 -1
  9. package/lib/client/createLogUsage.js +1 -1
  10. package/lib/client/sentry/captureSentryError.js +43 -0
  11. package/lib/client/sentry/initSentryClient.js +55 -0
  12. package/lib/client/sentry/setSentryUser.js +41 -0
  13. package/lib/server/apiWrapper.js +23 -5
  14. package/lib/server/auth/getAuthOptions.js +2 -2
  15. package/lib/server/auth/getServerSession.js +2 -2
  16. package/lib/server/fileCache.js +1 -1
  17. package/lib/server/log/createHandleError.js +151 -0
  18. package/lib/server/log/createLogger.js +3 -5
  19. package/lib/server/log/logRequest.js +1 -1
  20. package/lib/server/sentry/captureSentryError.js +57 -0
  21. package/lib/server/sentry/initSentry.js +44 -0
  22. package/lib/server/sentry/setSentryUser.js +46 -0
  23. package/lib/server/serverSidePropsWrapper.js +8 -4
  24. package/lowdefy/build.mjs +13 -10
  25. package/lowdefy/createCustomPluginTypesMap.mjs +1 -1
  26. package/next.config.js +12 -1
  27. package/package.json +24 -20
  28. package/package.original.json +24 -20
  29. package/pages/404.js +2 -2
  30. package/pages/[pageId].js +1 -1
  31. package/pages/_app.js +26 -3
  32. package/pages/_document.js +3 -3
  33. package/pages/api/auth/[...nextauth].js +2 -2
  34. package/pages/api/client-error.js +58 -0
  35. package/pages/api/endpoints/[endpointId].js +1 -1
  36. package/pages/api/request/[pageId]/[requestId].js +3 -3
  37. package/pages/api/usage.js +2 -2
  38. package/pages/index.js +1 -1
  39. package/lib/server/log/logError.js +0 -72
@@ -0,0 +1,19 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+ import { serializer } from '@lowdefy/helpers';
17
+ import raw from '../../build/app.json';
18
+
19
+ export default serializer.deserialize(raw);
@@ -0,0 +1,19 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+ import { serializer } from '@lowdefy/helpers';
17
+ import raw from '../../build/auth.json';
18
+
19
+ export default serializer.deserialize(raw);
@@ -0,0 +1,19 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+ import { serializer } from '@lowdefy/helpers';
17
+ import raw from '../../build/config.json';
18
+
19
+ export default serializer.deserialize(raw);
@@ -0,0 +1,19 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+ import { serializer } from '@lowdefy/helpers';
17
+ import raw from '../../build/logger.json';
18
+
19
+ export default serializer.deserialize(raw);
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright 2020-2024 Lowdefy, Inc
2
+ Copyright 2020-2026 Lowdefy, Inc
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright 2020-2024 Lowdefy, Inc
2
+ Copyright 2020-2026 Lowdefy, Inc
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -20,7 +20,7 @@ import React from 'react';
20
20
  import AuthConfigured from './AuthConfigured.js';
21
21
  import AuthNotConfigured from './AuthNotConfigured.js';
22
22
 
23
- import authConfig from '../../../build/auth.json';
23
+ import authConfig from '../../build/auth.js';
24
24
 
25
25
  function Auth({ children, session }) {
26
26
  if (authConfig.configured === true) {
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright 2020-2024 Lowdefy, Inc
2
+ Copyright 2020-2026 Lowdefy, Inc
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -19,7 +19,7 @@
19
19
  import React, { useEffect, useRef } from 'react';
20
20
  import { getSession, SessionProvider, signIn, signOut, useSession } from 'next-auth/react';
21
21
 
22
- import lowdefyConfig from '../../../build/config.json';
22
+ import lowdefyConfig from '../../build/config.js';
23
23
 
24
24
  function Session({ children }) {
25
25
  const wasAuthenticated = useRef(false);
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright 2020-2024 Lowdefy, Inc
2
+ Copyright 2020-2026 Lowdefy, Inc
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright 2020-2024 Lowdefy, Inc
2
+ Copyright 2020-2026 Lowdefy, Inc
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -0,0 +1,43 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import * as Sentry from '@sentry/nextjs';
18
+
19
+ function captureSentryError({ error, pageId, blockId, configLocation }) {
20
+ const tags = {};
21
+ const extra = {};
22
+
23
+ // Add Lowdefy-specific context
24
+ if (pageId) {
25
+ tags.pageId = pageId;
26
+ }
27
+
28
+ if (blockId) {
29
+ tags.blockId = blockId;
30
+ }
31
+
32
+ // Add config location context
33
+ if (configLocation) {
34
+ extra.configLocation = configLocation;
35
+ }
36
+
37
+ Sentry.captureException(error, {
38
+ tags,
39
+ extra,
40
+ });
41
+ }
42
+
43
+ export default captureSentryError;
@@ -0,0 +1,55 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import * as Sentry from '@sentry/nextjs';
18
+
19
+ let initialized = false;
20
+
21
+ function initSentryClient({ sentryDsn, sentryConfig }) {
22
+ // No-op if already initialized
23
+ if (initialized) {
24
+ return;
25
+ }
26
+
27
+ // No-op if SENTRY_DSN not set
28
+ if (!sentryDsn) {
29
+ return;
30
+ }
31
+
32
+ // No-op if client logging is explicitly disabled
33
+ if (sentryConfig?.client === false) {
34
+ return;
35
+ }
36
+
37
+ const config = sentryConfig || {};
38
+
39
+ Sentry.init({
40
+ dsn: sentryDsn,
41
+ environment: config.environment || process.env.NODE_ENV || 'production',
42
+ tracesSampleRate: config.tracesSampleRate ?? 0.1,
43
+ replaysSessionSampleRate: config.replaysSessionSampleRate ?? 0,
44
+ replaysOnErrorSampleRate: config.replaysOnErrorSampleRate ?? 0.1,
45
+ integrations: [
46
+ Sentry.replayIntegration(),
47
+ ...(config.feedback ? [Sentry.feedbackIntegration({ colorScheme: 'system' })] : []),
48
+ ],
49
+ });
50
+
51
+ initialized = true;
52
+ console.log('Sentry enabled: client');
53
+ }
54
+
55
+ export default initSentryClient;
@@ -0,0 +1,41 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import * as Sentry from '@sentry/nextjs';
18
+
19
+ function setSentryUser({ user, sentryConfig }) {
20
+ // No-op if no user
21
+ if (!user) {
22
+ Sentry.setUser(null);
23
+ return;
24
+ }
25
+
26
+ const userFields = sentryConfig?.userFields || ['id', '_id'];
27
+ const sentryUser = {};
28
+
29
+ userFields.forEach((field) => {
30
+ if (user[field] !== undefined) {
31
+ sentryUser[field] = user[field];
32
+ }
33
+ });
34
+
35
+ // Only set user if we have at least one field
36
+ if (Object.keys(sentryUser).length > 0) {
37
+ Sentry.setUser(sentryUser);
38
+ }
39
+ }
40
+
41
+ export default setSentryUser;
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright 2020-2024 Lowdefy, Inc
2
+ Copyright 2020-2026 Lowdefy, Inc
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -17,18 +17,21 @@
17
17
  import path from 'path';
18
18
  import { createApiContext } from '@lowdefy/api';
19
19
  import { getSecretsFromEnv } from '@lowdefy/node-utils';
20
+ import { serializer } from '@lowdefy/helpers';
20
21
  import { v4 as uuid } from 'uuid';
21
22
 
22
- import config from '../../build/config.json';
23
+ import config from '../build/config.js';
23
24
  import connections from '../../build/plugins/connections.js';
24
25
  import createLogger from './log/createLogger.js';
25
26
  import fileCache from './fileCache.js';
26
27
  import getServerSession from './auth/getServerSession.js';
27
- import logError from './log/logError.js';
28
+ import createHandleError from './log/createHandleError.js';
28
29
  import logRequest from './log/logRequest.js';
29
30
  import operators from '../../build/plugins/operators/server.js';
30
31
  import jsMap from '../../build/plugins/operators/serverJsMap.js';
31
32
  import getAuthOptions from './auth/getAuthOptions.js';
33
+ import loggerConfig from '../build/logger.js';
34
+ import setSentryUser from './sentry/setSentryUser.js';
32
35
 
33
36
  const secrets = getSecretsFromEnv();
34
37
 
@@ -43,6 +46,9 @@ function apiWrapper(handler) {
43
46
  fileCache,
44
47
  headers: req?.headers,
45
48
  jsMap,
49
+ handleError: async (err) => {
50
+ console.error(err);
51
+ },
46
52
  logger: console,
47
53
  operators,
48
54
  req,
@@ -51,9 +57,15 @@ function apiWrapper(handler) {
51
57
  };
52
58
  try {
53
59
  context.logger = createLogger({ rid: context.rid });
60
+ context.handleError = createHandleError({ context });
54
61
  context.authOptions = getAuthOptions(context);
55
62
  if (!req.url.startsWith('/api/auth')) {
56
63
  context.session = await getServerSession(context);
64
+ // Set Sentry user context for authenticated requests
65
+ setSentryUser({
66
+ user: context.session?.user,
67
+ sentryConfig: loggerConfig.sentry,
68
+ });
57
69
  }
58
70
  createApiContext(context);
59
71
  logRequest({ context });
@@ -61,8 +73,14 @@ function apiWrapper(handler) {
61
73
  const response = await handler({ context, req, res });
62
74
  return response;
63
75
  } catch (error) {
64
- logError({ error, context });
65
- res.status(500).json({ name: error.name, message: error.message });
76
+ await context.handleError(error);
77
+ const serialized = serializer.serialize(error);
78
+ if (serialized?.['~e']) {
79
+ delete serialized['~e'].received;
80
+ delete serialized['~e'].stack;
81
+ delete serialized['~e'].configKey;
82
+ }
83
+ res.status(500).json(serialized);
66
84
  }
67
85
  };
68
86
  }
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright 2020-2024 Lowdefy, Inc
2
+ Copyright 2020-2026 Lowdefy, Inc
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -18,7 +18,7 @@ import { getNextAuthConfig } from '@lowdefy/api';
18
18
  import { getSecretsFromEnv } from '@lowdefy/node-utils';
19
19
 
20
20
  import adapters from '../../../build/plugins/auth/adapters.js';
21
- import authJson from '../../../build/auth.json';
21
+ import authJson from '../../build/auth.js';
22
22
  import callbacks from '../../../build/plugins/auth/callbacks.js';
23
23
  import events from '../../../build/plugins/auth/events.js';
24
24
  import providers from '../../../build/plugins/auth/providers.js';
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright 2020-2024 Lowdefy, Inc
2
+ Copyright 2020-2026 Lowdefy, Inc
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -16,7 +16,7 @@
16
16
 
17
17
  import { getServerSession as getNextAuthServerSession } from 'next-auth/next';
18
18
 
19
- import authJson from '../../../build/auth.json';
19
+ import authJson from '../../build/auth.js';
20
20
 
21
21
  function getServerSession({ authOptions, req, res }) {
22
22
  if (authJson.configured === true) {
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright 2020-2024 Lowdefy, Inc
2
+ Copyright 2020-2026 Lowdefy, Inc
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -0,0 +1,151 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import {
18
+ ActionError,
19
+ BlockError,
20
+ ConfigError,
21
+ LowdefyInternalError,
22
+ OperatorError,
23
+ PluginError,
24
+ RequestError,
25
+ loadAndResolveErrorLocation,
26
+ ServiceError,
27
+ } from '@lowdefy/errors';
28
+
29
+ import captureSentryError from '../sentry/captureSentryError.js';
30
+
31
+ function getEventType(error) {
32
+ if (error instanceof ServiceError) {
33
+ return 'service_error';
34
+ }
35
+ if (error instanceof OperatorError) {
36
+ return 'operator_error';
37
+ }
38
+ if (error instanceof ActionError) {
39
+ return 'action_error';
40
+ }
41
+ if (error instanceof RequestError) {
42
+ return 'request_error';
43
+ }
44
+ if (error instanceof BlockError) {
45
+ return 'block_error';
46
+ }
47
+ if (error instanceof PluginError) {
48
+ return 'plugin_error';
49
+ }
50
+ if (error instanceof ConfigError) {
51
+ return 'config_error';
52
+ }
53
+ if (error instanceof LowdefyInternalError) {
54
+ return 'lowdefy_error';
55
+ }
56
+ return 'error';
57
+ }
58
+
59
+ function createHandleError({ context }) {
60
+ return async function handleError(error) {
61
+ try {
62
+ const { headers = {}, user = {} } = context;
63
+ const eventType = getEventType(error);
64
+ const isServiceError = error instanceof ServiceError;
65
+ const isLowdefyInternalError = error instanceof LowdefyInternalError;
66
+
67
+ // For internal lowdefy errors, don't resolve config location
68
+ const location = isLowdefyInternalError
69
+ ? null
70
+ : await loadAndResolveErrorLocation({
71
+ error,
72
+ readConfigFile: context.readConfigFile,
73
+ configDirectory: context.configDirectory,
74
+ });
75
+
76
+ // Attach resolved location to error for consistency
77
+ if (location) {
78
+ error.source = location.source;
79
+ error.config = location.config;
80
+ }
81
+
82
+ // Single structured log call — pino serializes error via extractErrorProps,
83
+ // message string gives human-readable display via CLI logger
84
+ const errorName = error?.name || 'Error';
85
+ context.logger.error(
86
+ {
87
+ err: error,
88
+ // Top-level fields for log drain consumers (duplicated from err for queryability)
89
+ event: eventType,
90
+ errorName,
91
+ errorMessage: error.message,
92
+ isServiceError,
93
+ pageId: context.pageId || null,
94
+ timestamp: new Date().toISOString(),
95
+ source: error.source || null,
96
+ config: error.config || null,
97
+ // Production fields
98
+ user: {
99
+ id: user.id,
100
+ roles: user.roles,
101
+ sub: user.sub,
102
+ session_id: user.session_id,
103
+ },
104
+ url: context.req.url,
105
+ method: context.req.method,
106
+ resolvedUrl: context.nextContext?.resolvedUrl,
107
+ hostname: context.req.hostname,
108
+ headers: {
109
+ 'accept-language': headers['accept-language'],
110
+ 'sec-ch-ua-mobile': headers['sec-ch-ua-mobile'],
111
+ 'sec-ch-ua-platform': headers['sec-ch-ua-platform'],
112
+ 'sec-ch-ua': headers['sec-ch-ua'],
113
+ 'user-agent': headers['user-agent'],
114
+ host: headers.host,
115
+ referer: headers.referer,
116
+ // Non localhost headers
117
+ 'x-forward-for': headers['x-forward-for'],
118
+ // Vercel headers
119
+ 'x-vercel-id': headers['x-vercel-id'],
120
+ 'x-real-ip': headers['x-real-ip'],
121
+ 'x-vercel-ip-country': headers['x-vercel-ip-country'],
122
+ 'x-vercel-ip-country-region': headers['x-vercel-ip-country-region'],
123
+ 'x-vercel-ip-city': headers['x-vercel-ip-city'],
124
+ 'x-vercel-ip-latitude': headers['x-vercel-ip-latitude'],
125
+ 'x-vercel-ip-longitude': headers['x-vercel-ip-longitude'],
126
+ 'x-vercel-ip-timezone': headers['x-vercel-ip-timezone'],
127
+ // Cloudflare headers
128
+ 'cf-connecting-ip': headers['cf-connecting-ip'],
129
+ 'cf-ray': headers['cf-ray'],
130
+ 'cf-ipcountry': headers['cf-ipcountry'],
131
+ 'cf-visitor': headers['cf-visitor'],
132
+ },
133
+ },
134
+ error.message
135
+ );
136
+
137
+ // Capture error to Sentry (no-op if Sentry not configured)
138
+ captureSentryError({
139
+ error,
140
+ context,
141
+ configLocation: location,
142
+ });
143
+ } catch (e) {
144
+ console.error(error);
145
+ console.error('An error occurred while logging the error.');
146
+ console.error(e);
147
+ }
148
+ };
149
+ }
150
+
151
+ export default createHandleError;
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright 2020-2024 Lowdefy, Inc
2
+ Copyright 2020-2026 Lowdefy, Inc
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -14,10 +14,8 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import pino from 'pino';
18
-
19
- // TODO: Pino does not serialize error.cause properties if the cause object is not an Error (or Error-like)
20
- const logger = pino({
17
+ import { createNodeLogger } from '@lowdefy/logger/node';
18
+ const logger = createNodeLogger({
21
19
  name: 'lowdefy_server',
22
20
  level: process.env.LOWDEFY_LOG_LEVEL ?? 'info',
23
21
  base: { pid: undefined, hostname: undefined },
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright 2020-2024 Lowdefy, Inc
2
+ Copyright 2020-2026 Lowdefy, Inc
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -0,0 +1,57 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import * as Sentry from '@sentry/nextjs';
18
+
19
+ function captureSentryError({ error, context, configLocation }) {
20
+ // No-op if Sentry not initialized (DSN not set)
21
+ if (!process.env.SENTRY_DSN) {
22
+ return;
23
+ }
24
+
25
+ const tags = {};
26
+ const extra = {};
27
+
28
+ // Add Lowdefy-specific context
29
+ if (context?.pageId) {
30
+ tags.pageId = context.pageId;
31
+ }
32
+
33
+ if (error?.blockId) {
34
+ tags.blockId = error.blockId;
35
+ }
36
+
37
+ if (error?.isServiceError !== undefined) {
38
+ tags.isServiceError = error.isServiceError;
39
+ }
40
+
41
+ // Add config location context
42
+ if (configLocation) {
43
+ extra.configLocation = configLocation;
44
+ }
45
+
46
+ // Add config key for reference
47
+ if (error?.configKey) {
48
+ extra.configKey = error.configKey;
49
+ }
50
+
51
+ Sentry.captureException(error, {
52
+ tags,
53
+ extra,
54
+ });
55
+ }
56
+
57
+ export default captureSentryError;
@@ -0,0 +1,44 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import * as Sentry from '@sentry/nextjs';
18
+
19
+ import loggerConfig from '../../build/logger.js';
20
+
21
+ function initSentryServer() {
22
+ // No-op if SENTRY_DSN not set
23
+ if (!process.env.SENTRY_DSN) {
24
+ return;
25
+ }
26
+
27
+ const sentryConfig = loggerConfig.sentry || {};
28
+
29
+ // No-op if server logging is explicitly disabled
30
+ if (sentryConfig.server === false) {
31
+ return;
32
+ }
33
+
34
+ Sentry.init({
35
+ dsn: process.env.SENTRY_DSN,
36
+ environment: sentryConfig.environment || process.env.NODE_ENV || 'production',
37
+ tracesSampleRate: sentryConfig.tracesSampleRate ?? 0.1,
38
+ });
39
+
40
+ console.log('Sentry enabled: server');
41
+ }
42
+
43
+ export default initSentryServer;
44
+ export { initSentryServer };