Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 16 additions & 0 deletions airflow/config_templates/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -979,6 +979,22 @@ logging:
type: boolean
example: ~
default: "True"
color_log_error_keywords:
Comment thread
jscheffl marked this conversation as resolved.
Outdated
description: |
A comma separated list of keywords related to errors whose presence should display the line in red
color in UI
version_added: 2.10.0
type: string
example: ~
default: "error,exception"
color_log_warning_keywords:
description: |
A comma separated list of keywords related to warning whose presence should display the line in yellow
color in UI
version_added: 2.10.0
type: string
example: ~
default: "warn"
metrics:
description: |
StatsD (https://github.com/etsy/statsd) integration settings.
Expand Down
15 changes: 15 additions & 0 deletions airflow/www/static/js/dag/details/taskInstance/Logs/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
/* global moment */

import { AnsiUp } from "ansi_up";
import { getMetaValue, highlightByKeywords } from "src/utils";
import { defaultFormatWithTZ } from "src/datetime_utils";

export enum LogLevel {
Expand All @@ -38,6 +39,15 @@ export const logLevelColorMapping = {
[LogLevel.CRITICAL]: "red.400",
};

const errorKeywords = getMetaValue("color_log_error_keywords")
.split(",")
.filter((keyword) => keyword.length > 0)
.map((keyword) => keyword.toLowerCase());
const warningKeywords = getMetaValue("color_log_warning_keywords")
.split(",")
.filter((keyword) => keyword.length > 0)
.map((keyword) => keyword.toLowerCase());

export const parseLogs = (
data: string | undefined,
timezone: string | null,
Expand Down Expand Up @@ -112,6 +122,11 @@ export const parseLogs = (
line.includes(fileSourceFilter)
)
) {
parsedLine = highlightByKeywords(
parsedLine,
errorKeywords,
warningKeywords
);
// for lines with color convert to nice HTML
const coloredLine = ansiUp.ansi_to_html(parsedLine);

Expand Down
49 changes: 48 additions & 1 deletion airflow/www/static/js/utils/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@

import { isEmpty } from "lodash";
import type { DagRun } from "src/types";
import { getDagRunLabel, getTask, getTaskSummary } from ".";
import {
getDagRunLabel,
getTask,
getTaskSummary,
highlightByKeywords,
} from ".";

const sampleTasks = {
id: null,
Expand Down Expand Up @@ -148,3 +153,45 @@ describe("Test getDagRunLabel", () => {
expect(runLabel).toBe(dagRun.executionDate);
});
});

describe("Test highlightByKeywords", () => {
test("Highlight error line by red color", async () => {
const originalLine = "line with Error";
const expected = `\x1b[1m\x1b[31mline with Error\x1b[39m\x1b[0m`;
const highlightedLine = highlightByKeywords(
originalLine,
["error"],
["warn"]
);
expect(highlightedLine).toBe(expected);
});
test("Highlight warning line by yellow color", async () => {
const originalLine = "line with Warning";
const expected = `\x1b[1m\x1b[33mline with Warning\x1b[39m\x1b[0m`;
const highlightedLine = highlightByKeywords(
originalLine,
["error"],
["warn"]
);
expect(highlightedLine).toBe(expected);
});
test("Highlight line by red color when line has both error and warning", async () => {
const originalLine = "line with error Warning";
const expected = `\x1b[1m\x1b[31mline with error Warning\x1b[39m\x1b[0m`;
const highlightedLine = highlightByKeywords(
originalLine,
["error"],
["warn"]
);
expect(highlightedLine).toBe(expected);
});
test("No highlight", async () => {
const originalLine = "sample line";
const highlightedLine = highlightByKeywords(
originalLine,
["error"],
["warn"]
);
expect(highlightedLine).toBe(originalLine);
});
});
29 changes: 29 additions & 0 deletions airflow/www/static/js/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,34 @@ const toSentenceCase = (camelCase: string): string => {
return "";
};

const highlightByKeywords = (
parsedLine: string,
errorKeywords: string[],
warningKeywords: string[]
): string => {
const lowerParsedLine = parsedLine.toLowerCase();
const red = (line: string) => `\x1b[1m\x1b[31m${line}\x1b[39m\x1b[0m`;
const yellow = (line: string) => `\x1b[1m\x1b[33m${line}\x1b[39m\x1b[0m`;

const containsError = errorKeywords.some((keyword) =>
lowerParsedLine.includes(keyword)
);

if (containsError) {
return red(parsedLine);
}

const containsWarning = warningKeywords.some((keyword) =>
Comment thread
bbovenzi marked this conversation as resolved.
Outdated
lowerParsedLine.includes(keyword)
);

if (containsWarning) {
return yellow(parsedLine);
}

return parsedLine;
};

export {
hoverDelay,
finalStatesMap,
Expand All @@ -197,4 +225,5 @@ export {
getStatusBackgroundColor,
useOffsetTop,
toSentenceCase,
highlightByKeywords,
};
2 changes: 2 additions & 0 deletions airflow/www/templates/airflow/grid.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
<meta name="base_date" content="{{ request.args.get('base_date') if request.args.get('base_date') else '' }}">
<meta name="default_wrap" content="{{ default_wrap }}">
<meta name="dataset_events_api" content="{{ url_for('/api/v1.airflow_api_connexion_endpoints_dataset_endpoint_get_dataset_events') }}">
<meta name="color_log_error_keywords" content="{{ color_log_error_keywords }}">
<meta name="color_log_warning_keywords" content="{{ color_log_warning_keywords }}">
{% endblock %}

{% block content %}
Expand Down
5 changes: 5 additions & 0 deletions airflow/www/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2786,6 +2786,9 @@ def legacy_tree(self):
@provide_session
def grid(self, dag_id: str, session: Session = NEW_SESSION):
"""Get Dag's grid view."""
color_log_error_keywords = conf.get("logging", "color_log_error_keywords", fallback="")
color_log_warning_keywords = conf.get("logging", "color_log_warning_keywords", fallback="")

dag = get_airflow_app().dag_bag.get_dag(dag_id, session=session)
dag_model = DagModel.get_dagmodel(dag_id, session=session)
if not dag:
Expand Down Expand Up @@ -2843,6 +2846,8 @@ def grid(self, dag_id: str, session: Session = NEW_SESSION):
),
included_events_raw=included_events_raw,
excluded_events_raw=excluded_events_raw,
color_log_error_keywords=color_log_error_keywords,
color_log_warning_keywords=color_log_warning_keywords,
)

@expose("/calendar")
Expand Down