-
Notifications
You must be signed in to change notification settings - Fork 8.6k
Added Enhancements to Log Ai Insight #247291
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
Changes from 14 commits
c9aab67
44b44e2
b64015b
a87c1dc
e5d48e5
e02947c
769fee0
e50dd81
52ee4df
ac070e8
b8c86ee
b0685ce
55affb9
0b45ec5
3124e3a
e1309f1
2112716
a57c8be
384bced
2523a21
35b7291
0e6ac58
2ad03d3
ab652c6
94135cc
35ec4bd
d9b647f
07ce96a
f4382fe
c5d3a6f
ae8568f
6fd1ce8
33b4b6b
6130807
f009254
5ce23e8
2423a38
e56b156
d70c2fd
c907227
5dc626c
73ccf32
bd60b60
cf98e3a
5c124c5
e9fd179
8a6da49
7bbe53d
b220f84
7719f35
15fa737
c389435
efb1d96
1dea11f
1973cbd
775b93c
ab0c124
01b7d1b
ca63002
53f8492
e95c9c2
0b5b0cd
58c53df
e6bee31
841558f
8d98f5c
3b2bda0
95c5a02
a4af6d0
f752200
5d835e2
306143a
90e3885
6c67d8d
b47a1e6
4ea551b
8f6049e
51a1ba8
f2faa93
cac6cfc
430b890
a3a965b
6500993
999af77
bbce724
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -6,11 +6,19 @@ | |||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||
| import moment from 'moment'; | ||||||||||||||||||||||||||||||||||||||||||||
| import { MessageRole, type InferenceClient } from '@kbn/inference-common'; | ||||||||||||||||||||||||||||||||||||||||||||
| import type { IScopedClusterClient, KibanaRequest } from '@kbn/core/server'; | ||||||||||||||||||||||||||||||||||||||||||||
| import type { IScopedClusterClient, KibanaRequest, CoreSetup, Logger } from '@kbn/core/server'; | ||||||||||||||||||||||||||||||||||||||||||||
| import { safeJsonStringify } from '@kbn/std'; | ||||||||||||||||||||||||||||||||||||||||||||
| import dedent from 'dedent'; | ||||||||||||||||||||||||||||||||||||||||||||
| import type { ObservabilityAgentBuilderDataRegistry } from '../../data_registry/data_registry'; | ||||||||||||||||||||||||||||||||||||||||||||
| import { getLogDocumentById } from './get_log_document_by_id'; | ||||||||||||||||||||||||||||||||||||||||||||
| import type { | ||||||||||||||||||||||||||||||||||||||||||||
| ObservabilityAgentBuilderPluginSetupDependencies, | ||||||||||||||||||||||||||||||||||||||||||||
| ObservabilityAgentBuilderPluginStart, | ||||||||||||||||||||||||||||||||||||||||||||
| ObservabilityAgentBuilderPluginStartDependencies, | ||||||||||||||||||||||||||||||||||||||||||||
| } from '../../types'; | ||||||||||||||||||||||||||||||||||||||||||||
| import { getApmIndices } from '../../utils/get_apm_indices'; | ||||||||||||||||||||||||||||||||||||||||||||
| import { fetchDistributedTrace } from './apm_error/fetch_distributed_trace'; | ||||||||||||||||||||||||||||||||||||||||||||
| import { parseDatemath } from '../../utils/time'; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| export interface GetLogAiInsightsParams { | ||||||||||||||||||||||||||||||||||||||||||||
| index: string; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -20,6 +28,12 @@ export interface GetLogAiInsightsParams { | |||||||||||||||||||||||||||||||||||||||||||
| connectorId: string; | ||||||||||||||||||||||||||||||||||||||||||||
| request: KibanaRequest; | ||||||||||||||||||||||||||||||||||||||||||||
| esClient: IScopedClusterClient; | ||||||||||||||||||||||||||||||||||||||||||||
| core: CoreSetup< | ||||||||||||||||||||||||||||||||||||||||||||
| ObservabilityAgentBuilderPluginStartDependencies, | ||||||||||||||||||||||||||||||||||||||||||||
| ObservabilityAgentBuilderPluginStart | ||||||||||||||||||||||||||||||||||||||||||||
| >; | ||||||||||||||||||||||||||||||||||||||||||||
|
yuliia-fryshko marked this conversation as resolved.
Outdated
|
||||||||||||||||||||||||||||||||||||||||||||
| plugins: ObservabilityAgentBuilderPluginSetupDependencies; | ||||||||||||||||||||||||||||||||||||||||||||
| logger: Logger; | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| export async function getLogAiInsights({ | ||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -30,10 +44,44 @@ export async function getLogAiInsights({ | |||||||||||||||||||||||||||||||||||||||||||
| dataRegistry, | ||||||||||||||||||||||||||||||||||||||||||||
| inferenceClient, | ||||||||||||||||||||||||||||||||||||||||||||
| connectorId, | ||||||||||||||||||||||||||||||||||||||||||||
| core, | ||||||||||||||||||||||||||||||||||||||||||||
| plugins, | ||||||||||||||||||||||||||||||||||||||||||||
| logger, | ||||||||||||||||||||||||||||||||||||||||||||
| }: GetLogAiInsightsParams): Promise<{ summary: string; context: string }> { | ||||||||||||||||||||||||||||||||||||||||||||
| const systemPrompt = dedent(` | ||||||||||||||||||||||||||||||||||||||||||||
| You are assisting an SRE who is viewing a log entry in the Kibana Logs UI. | ||||||||||||||||||||||||||||||||||||||||||||
| Using the provided data produce a concise, action-oriented response.`); | ||||||||||||||||||||||||||||||||||||||||||||
| You are an expert SRE assistant helping a user analyze a log entry in Kibana. Your goal is to provide actionable insights tailored to the log's severity and content. | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| ## Analysis Approach | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| 1. **Classify the log by log.level**: Determine the log level from the log.level field (error, warning, info, debug, trace, fatal, critical, alert, emergency). | ||||||||||||||||||||||||||||||||||||||||||||
| **Important**: Even if log.level is "info", "debug", or "trace", if the log mentions error messages, error fields, or exception fields, treat it as an error-level log. | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| 2. **Respond based on log level:** | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| ### Error/Fatal/Critical/Alert/Emergency Logs (or logs with error/exception fields) | ||||||||||||||||||||||||||||||||||||||||||||
| Provide a thorough investigation: | ||||||||||||||||||||||||||||||||||||||||||||
| - **What happened**: Summarize the error in plain language | ||||||||||||||||||||||||||||||||||||||||||||
| - **Where it originated**: Identify the service, component, or code path | ||||||||||||||||||||||||||||||||||||||||||||
| - **Root cause**: Analyze using ServiceSummary, DownstreamDependencies, TraceDocuments, and TraceServices if available | ||||||||||||||||||||||||||||||||||||||||||||
| - **Impact**: Note any affected downstream services or dependencies | ||||||||||||||||||||||||||||||||||||||||||||
| - **Next steps**: Suggest specific actions for investigation or remediation | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| ### Warning Logs | ||||||||||||||||||||||||||||||||||||||||||||
| - Assess whether the warning indicates an emerging problem or is purely informational | ||||||||||||||||||||||||||||||||||||||||||||
| - If it signals a potential issue, analyze it like an error log (see above) | ||||||||||||||||||||||||||||||||||||||||||||
| - If it's routine, provide a brief explanation of what triggered it | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| ### Info/Debug/Trace Logs | ||||||||||||||||||||||||||||||||||||||||||||
| Keep it concise: | ||||||||||||||||||||||||||||||||||||||||||||
| - Explain what the log message means in context | ||||||||||||||||||||||||||||||||||||||||||||
| - Identify the source (service, host, container) | ||||||||||||||||||||||||||||||||||||||||||||
| - Only flag concerns if the content unexpectedly suggests a problem | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| ## Important Notes | ||||||||||||||||||||||||||||||||||||||||||||
| - Base your analysis strictly on the provided data (**DO NOT** speculate beyond what the evidence shows) | ||||||||||||||||||||||||||||||||||||||||||||
| - Be specific: reference actual field values, timestamps, and service names from the log entry | ||||||||||||||||||||||||||||||||||||||||||||
| - Prioritize actionable information over generic advice | ||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a lot of text (tokens) talking about what seems like the same thing. Can you reduce it? Also there is no need to say if(logEntry.level === "info") {
systemPrompt = 'This log is an info log...'
}Btw:
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you, @sorenlouv. I’ve added logic to distinguish log levels and updated the system prompt based on that. |
||||||||||||||||||||||||||||||||||||||||||||
| `); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| const logEntry = await getLogDocumentById({ | ||||||||||||||||||||||||||||||||||||||||||||
| esClient: esClient.asCurrentUser, | ||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -45,6 +93,14 @@ export async function getLogAiInsights({ | |||||||||||||||||||||||||||||||||||||||||||
| throw new Error('Log entry not found'); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| const resourceAttributes = logEntry.resource?.attributes; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| const serviceName = | ||||||||||||||||||||||||||||||||||||||||||||
| logEntry.service?.name ?? (resourceAttributes?.['service.name'] as string) ?? ''; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| const serviceEnvironment = | ||||||||||||||||||||||||||||||||||||||||||||
| logEntry.service?.environment ?? (resourceAttributes?.['service.environment'] as string) ?? ''; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| let context = dedent(` | ||||||||||||||||||||||||||||||||||||||||||||
| <LogEntryIndex> | ||||||||||||||||||||||||||||||||||||||||||||
| ${index} | ||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -59,33 +115,119 @@ export async function getLogAiInsights({ | |||||||||||||||||||||||||||||||||||||||||||
| </LogEntryFields> | ||||||||||||||||||||||||||||||||||||||||||||
| `); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| if (logEntry.service?.name && logEntry.service?.environment) { | ||||||||||||||||||||||||||||||||||||||||||||
| const serviceSummary = await dataRegistry.getData('apmServiceSummary', { | ||||||||||||||||||||||||||||||||||||||||||||
| request, | ||||||||||||||||||||||||||||||||||||||||||||
| serviceName: logEntry.service?.name, | ||||||||||||||||||||||||||||||||||||||||||||
| serviceEnvironment: logEntry.service?.environment, | ||||||||||||||||||||||||||||||||||||||||||||
| start: moment(logEntry['@timestamp']).subtract(24, 'hours').toISOString(), | ||||||||||||||||||||||||||||||||||||||||||||
| end: moment(logEntry['@timestamp']).add(24, 'hours').toISOString(), | ||||||||||||||||||||||||||||||||||||||||||||
| if (serviceName) { | ||||||||||||||||||||||||||||||||||||||||||||
| const logTimestamp = logEntry['@timestamp'] as string; | ||||||||||||||||||||||||||||||||||||||||||||
| const start = moment(logTimestamp).subtract(24, 'hours').toISOString(); | ||||||||||||||||||||||||||||||||||||||||||||
| const end = moment(logTimestamp).add(24, 'hours').toISOString(); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| interface ContextPart { | ||||||||||||||||||||||||||||||||||||||||||||
| name: string; | ||||||||||||||||||||||||||||||||||||||||||||
| handler: () => Promise<unknown>; | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| const contextParts: ContextPart[] = [ | ||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||
| name: 'ServiceSummary', | ||||||||||||||||||||||||||||||||||||||||||||
| handler: () => | ||||||||||||||||||||||||||||||||||||||||||||
| dataRegistry.getData('apmServiceSummary', { | ||||||||||||||||||||||||||||||||||||||||||||
| request, | ||||||||||||||||||||||||||||||||||||||||||||
| serviceName, | ||||||||||||||||||||||||||||||||||||||||||||
| serviceEnvironment, | ||||||||||||||||||||||||||||||||||||||||||||
| start, | ||||||||||||||||||||||||||||||||||||||||||||
| end, | ||||||||||||||||||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||
| name: 'DownstreamDependencies', | ||||||||||||||||||||||||||||||||||||||||||||
| handler: () => | ||||||||||||||||||||||||||||||||||||||||||||
| dataRegistry.getData('apmDownstreamDependencies', { | ||||||||||||||||||||||||||||||||||||||||||||
| request, | ||||||||||||||||||||||||||||||||||||||||||||
| serviceName, | ||||||||||||||||||||||||||||||||||||||||||||
| serviceEnvironment, | ||||||||||||||||||||||||||||||||||||||||||||
| start, | ||||||||||||||||||||||||||||||||||||||||||||
| end, | ||||||||||||||||||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| const traceId = (logEntry.trace_id as string) || (logEntry.trace as { id?: string })?.id; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| if (traceId) { | ||||||||||||||||||||||||||||||||||||||||||||
| const parsedStart = parseDatemath(start, { roundUp: false }); | ||||||||||||||||||||||||||||||||||||||||||||
| const parsedEnd = parseDatemath(end, { roundUp: true }); | ||||||||||||||||||||||||||||||||||||||||||||
| const traceContextPromise = (async () => { | ||||||||||||||||||||||||||||||||||||||||||||
| const apmIndices = await getApmIndices({ core, plugins, logger }); | ||||||||||||||||||||||||||||||||||||||||||||
| return fetchDistributedTrace({ | ||||||||||||||||||||||||||||||||||||||||||||
| esClient, | ||||||||||||||||||||||||||||||||||||||||||||
| apmIndices, | ||||||||||||||||||||||||||||||||||||||||||||
| traceId, | ||||||||||||||||||||||||||||||||||||||||||||
| start: parsedStart, | ||||||||||||||||||||||||||||||||||||||||||||
| end: parsedEnd, | ||||||||||||||||||||||||||||||||||||||||||||
| logger, | ||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||
| })(); | ||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: IIFE's is a somewhat confusing pattern. I think readability will improve with
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| contextParts.push({ | ||||||||||||||||||||||||||||||||||||||||||||
| name: 'TraceDocuments', | ||||||||||||||||||||||||||||||||||||||||||||
| handler: async () => (await traceContextPromise).traceDocuments, | ||||||||||||||||||||||||||||||||||||||||||||
|
viduni94 marked this conversation as resolved.
Outdated
|
||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| contextParts.push({ | ||||||||||||||||||||||||||||||||||||||||||||
| name: 'TraceServices', | ||||||||||||||||||||||||||||||||||||||||||||
| handler: async () => (await traceContextPromise).services, | ||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| const results = await Promise.allSettled( | ||||||||||||||||||||||||||||||||||||||||||||
| contextParts.map(async (part) => { | ||||||||||||||||||||||||||||||||||||||||||||
| const data = await part.handler(); | ||||||||||||||||||||||||||||||||||||||||||||
| return { part, data }; | ||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| const contextPartsStrings: string[] = []; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| results.forEach((result) => { | ||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: can you use |
||||||||||||||||||||||||||||||||||||||||||||
| if (result.status === 'rejected') { | ||||||||||||||||||||||||||||||||||||||||||||
| logger.debug(`Log AI Insight: fetch failed: ${result.reason}`); | ||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| const { part, data } = result.value; | ||||||||||||||||||||||||||||||||||||||||||||
| if (!data || (Array.isArray(data) && data.length === 0)) { | ||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| contextPartsStrings.push( | ||||||||||||||||||||||||||||||||||||||||||||
| dedent(` | ||||||||||||||||||||||||||||||||||||||||||||
| <${part.name}> | ||||||||||||||||||||||||||||||||||||||||||||
| Time window: ${start} to ${end} | ||||||||||||||||||||||||||||||||||||||||||||
| \`\`\`json | ||||||||||||||||||||||||||||||||||||||||||||
| ${safeJsonStringify(data)} | ||||||||||||||||||||||||||||||||||||||||||||
| \`\`\` | ||||||||||||||||||||||||||||||||||||||||||||
| </${part.name}> | ||||||||||||||||||||||||||||||||||||||||||||
| `) | ||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||
| context += dedent(` | ||||||||||||||||||||||||||||||||||||||||||||
| <ServiceSummary> | ||||||||||||||||||||||||||||||||||||||||||||
| \`\`\`json | ||||||||||||||||||||||||||||||||||||||||||||
| ${safeJsonStringify(serviceSummary)} | ||||||||||||||||||||||||||||||||||||||||||||
| \`\`\` | ||||||||||||||||||||||||||||||||||||||||||||
| </ServiceSummary> | ||||||||||||||||||||||||||||||||||||||||||||
| `); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| if (contextPartsStrings.length > 0) { | ||||||||||||||||||||||||||||||||||||||||||||
| context += contextPartsStrings.join('\n\n'); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| const response = await inferenceClient.chatComplete({ | ||||||||||||||||||||||||||||||||||||||||||||
| connectorId, | ||||||||||||||||||||||||||||||||||||||||||||
| system: systemPrompt, | ||||||||||||||||||||||||||||||||||||||||||||
| messages: [ | ||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||
| role: MessageRole.User, | ||||||||||||||||||||||||||||||||||||||||||||
| content: | ||||||||||||||||||||||||||||||||||||||||||||
| dedent(`Explain this log message: what it means, where it is from, whether it is expected, and if it is an issue. | ||||||||||||||||||||||||||||||||||||||||||||
| content: dedent(` | ||||||||||||||||||||||||||||||||||||||||||||
| <LogContext> | ||||||||||||||||||||||||||||||||||||||||||||
| ${context} | ||||||||||||||||||||||||||||||||||||||||||||
| `), | ||||||||||||||||||||||||||||||||||||||||||||
| </LogContext> | ||||||||||||||||||||||||||||||||||||||||||||
| Analyze this log entry and generate a summary explaining what it means, whether this is expected behavior or indicates an issue (e.g., error, warning, exception, service failure, etc.). | ||||||||||||||||||||||||||||||||||||||||||||
| If it indicates an issue, explain the likely root cause if determinable and recommended actions or steps for further investigation. | ||||||||||||||||||||||||||||||||||||||||||||
| Follow your system instructions. Ensure the analysis is grounded in the provided context, and concise. | ||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does "Follow your system instructions" have an effect?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch, I don't think it's very needed |
||||||||||||||||||||||||||||||||||||||||||||
| `), | ||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.