feat(server): improve logs (#8977)

This commit is contained in:
darkskygit
2024-12-03 06:47:16 +00:00
parent 27d2735a64
commit eb64231899
8 changed files with 35 additions and 16 deletions
@@ -75,7 +75,7 @@ export class AuthController {
@Body() params?: { email: string } @Body() params?: { email: string }
): Promise<PreflightResponse> { ): Promise<PreflightResponse> {
if (!params?.email) { if (!params?.email) {
throw new InvalidEmail(); throw new InvalidEmail({ email: 'not provided' });
} }
validators.assertValidEmail(params.email); validators.assertValidEmail(params.email);
@@ -171,7 +171,7 @@ export class AuthController {
// verify domain has MX, SPF, DMARC records // verify domain has MX, SPF, DMARC records
const [name, domain, ...rest] = email.split('@'); const [name, domain, ...rest] = email.split('@');
if (rest.length || !domain) { if (rest.length || !domain) {
throw new InvalidEmail(); throw new InvalidEmail({ email });
} }
const [mx, spf, dmarc] = await Promise.allSettled([ const [mx, spf, dmarc] = await Promise.allSettled([
resolveMx(domain).then(t => t.map(mx => mx.exchange).filter(Boolean)), resolveMx(domain).then(t => t.map(mx => mx.exchange).filter(Boolean)),
@@ -183,11 +183,11 @@ export class AuthController {
), ),
]).then(t => t.filter(t => t.status === 'fulfilled').map(t => t.value)); ]).then(t => t.filter(t => t.status === 'fulfilled').map(t => t.value));
if (!mx?.length || !spf?.length || !dmarc?.length) { if (!mx?.length || !spf?.length || !dmarc?.length) {
throw new InvalidEmail(); throw new InvalidEmail({ email });
} }
// filter out alias emails // filter out alias emails
if (name.includes('+') || name.includes('.')) { if (name.includes('+') || name.includes('.')) {
throw new InvalidEmail(); throw new InvalidEmail({ email });
} }
} }
} }
@@ -5,7 +5,7 @@ import { InvalidEmail, InvalidPasswordLength } from '../../fundamentals';
export function assertValidEmail(email: string) { export function assertValidEmail(email: string) {
const result = z.string().email().safeParse(email); const result = z.string().email().safeParse(email);
if (!result.success) { if (!result.success) {
throw new InvalidEmail(); throw new InvalidEmail({ email });
} }
} }
@@ -101,7 +101,12 @@ export class UserFriendlyError extends Error {
log(context: string) { log(context: string) {
// ignore all user behavior error log // ignore all user behavior error log
if (this.type !== 'internal_server_error') { if (this.type !== 'internal_server_error') {
return; // always record auth related error
const isAuthError =
typeof this.stack === 'string' &&
(this.stack.includes('/core/auth/') ||
this.stack.includes('/plugins/oauth/'));
if (!isAuthError) return;
} }
new Logger(context).error( new Logger(context).error(
@@ -261,7 +266,8 @@ export const USER_FRIENDLY_ERRORS = {
}, },
invalid_email: { invalid_email: {
type: 'invalid_input', type: 'invalid_input',
message: 'An invalid email provided.', args: { email: 'string' },
message: ({ email }) => `An invalid email provided: ${email}`,
}, },
invalid_password_length: { invalid_password_length: {
type: 'invalid_input', type: 'invalid_input',
@@ -89,10 +89,14 @@ export class OauthAccountAlreadyConnected extends UserFriendlyError {
super('bad_request', 'oauth_account_already_connected', message); super('bad_request', 'oauth_account_already_connected', message);
} }
} }
@ObjectType()
class InvalidEmailDataType {
@Field() email!: string
}
export class InvalidEmail extends UserFriendlyError { export class InvalidEmail extends UserFriendlyError {
constructor(message?: string) { constructor(args: InvalidEmailDataType, message?: string | ((args: InvalidEmailDataType) => string)) {
super('invalid_input', 'invalid_email', message); super('invalid_input', 'invalid_email', message, args);
} }
} }
@ObjectType() @ObjectType()
@@ -620,5 +624,5 @@ registerEnumType(ErrorNames, {
export const ErrorDataUnionType = createUnionType({ export const ErrorDataUnionType = createUnionType({
name: 'ErrorDataUnion', name: 'ErrorDataUnion',
types: () => types: () =>
[UnknownOauthProviderDataType, MissingOauthQueryParameterDataType, InvalidPasswordLengthDataType, SpaceNotFoundDataType, NotInSpaceDataType, AlreadyInSpaceDataType, SpaceAccessDeniedDataType, SpaceOwnerNotFoundDataType, DocNotFoundDataType, DocAccessDeniedDataType, VersionRejectedDataType, InvalidHistoryTimestampDataType, DocHistoryNotFoundDataType, BlobNotFoundDataType, SubscriptionAlreadyExistsDataType, SubscriptionNotExistsDataType, SameSubscriptionRecurringDataType, SubscriptionPlanNotFoundDataType, CopilotMessageNotFoundDataType, CopilotPromptNotFoundDataType, CopilotProviderSideErrorDataType, RuntimeConfigNotFoundDataType, InvalidRuntimeConfigTypeDataType] as const, [UnknownOauthProviderDataType, MissingOauthQueryParameterDataType, InvalidEmailDataType, InvalidPasswordLengthDataType, SpaceNotFoundDataType, NotInSpaceDataType, AlreadyInSpaceDataType, SpaceAccessDeniedDataType, SpaceOwnerNotFoundDataType, DocNotFoundDataType, DocAccessDeniedDataType, VersionRejectedDataType, InvalidHistoryTimestampDataType, DocHistoryNotFoundDataType, BlobNotFoundDataType, SubscriptionAlreadyExistsDataType, SubscriptionNotExistsDataType, SameSubscriptionRecurringDataType, SubscriptionPlanNotFoundDataType, CopilotMessageNotFoundDataType, CopilotPromptNotFoundDataType, CopilotProviderSideErrorDataType, RuntimeConfigNotFoundDataType, InvalidRuntimeConfigTypeDataType] as const,
}); });
@@ -43,9 +43,12 @@ export class GQLLoggerPlugin implements ApolloServerPlugin {
); );
error.log('GraphQL'); error.log('GraphQL');
metrics.gql metrics.gql.counter('query_error_counter').add(1, {
.counter('query_error_counter') operation,
.add(1, { operation, code: error.status }); code: error.status,
type: error.type,
error: error.name,
});
}); });
return Promise.resolve(); return Promise.resolve();
@@ -46,7 +46,9 @@ export class GlobalExceptionFilter extends BaseExceptionFilter {
throw error; throw error;
} else { } else {
error.log('HTTP'); error.log('HTTP');
metrics.controllers.counter('error').add(1, { status: error.status }); metrics.controllers
.counter('error')
.add(1, { status: error.status, type: error.type, error: error.name });
const res = host.switchToHttp().getResponse<Response>(); const res = host.switchToHttp().getResponse<Response>();
const respondText = res.getHeader('content-type') === 'text/plain'; const respondText = res.getHeader('content-type') === 'text/plain';
+5 -1
View File
@@ -208,7 +208,7 @@ type EditorType {
name: String! name: String!
} }
union ErrorDataUnion = AlreadyInSpaceDataType | BlobNotFoundDataType | CopilotMessageNotFoundDataType | CopilotPromptNotFoundDataType | CopilotProviderSideErrorDataType | DocAccessDeniedDataType | DocHistoryNotFoundDataType | DocNotFoundDataType | InvalidHistoryTimestampDataType | InvalidPasswordLengthDataType | InvalidRuntimeConfigTypeDataType | MissingOauthQueryParameterDataType | NotInSpaceDataType | RuntimeConfigNotFoundDataType | SameSubscriptionRecurringDataType | SpaceAccessDeniedDataType | SpaceNotFoundDataType | SpaceOwnerNotFoundDataType | SubscriptionAlreadyExistsDataType | SubscriptionNotExistsDataType | SubscriptionPlanNotFoundDataType | UnknownOauthProviderDataType | VersionRejectedDataType union ErrorDataUnion = AlreadyInSpaceDataType | BlobNotFoundDataType | CopilotMessageNotFoundDataType | CopilotPromptNotFoundDataType | CopilotProviderSideErrorDataType | DocAccessDeniedDataType | DocHistoryNotFoundDataType | DocNotFoundDataType | InvalidEmailDataType | InvalidHistoryTimestampDataType | InvalidPasswordLengthDataType | InvalidRuntimeConfigTypeDataType | MissingOauthQueryParameterDataType | NotInSpaceDataType | RuntimeConfigNotFoundDataType | SameSubscriptionRecurringDataType | SpaceAccessDeniedDataType | SpaceNotFoundDataType | SpaceOwnerNotFoundDataType | SubscriptionAlreadyExistsDataType | SubscriptionNotExistsDataType | SubscriptionPlanNotFoundDataType | UnknownOauthProviderDataType | VersionRejectedDataType
enum ErrorNames { enum ErrorNames {
ACCESS_DENIED ACCESS_DENIED
@@ -315,6 +315,10 @@ type HumanReadableQuotaType {
storageQuota: String! storageQuota: String!
} }
type InvalidEmailDataType {
email: String!
}
type InvalidHistoryTimestampDataType { type InvalidHistoryTimestampDataType {
timestamp: String! timestamp: String!
} }
@@ -123,7 +123,7 @@ test('should not be able to sign in if email is invalid', async t => {
.send({ email: '' }) .send({ email: '' })
.expect(400); .expect(400);
t.is(res.body.message, 'An invalid email provided.'); t.is(res.body.message, 'An invalid email provided: ');
}); });
test('should not be able to sign in if forbidden', async t => { test('should not be able to sign in if forbidden', async t => {