diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0ca2356ba..50888efed 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,17 @@
# Changelog
+## [next]
+
+### Fixed
+
+- Messages are now logged correctly when both `--log.format=json` and
+ `--log.timestamp=true` flags are set #3822.
+
+### Changed
+
+- Show a placeholder message if no alertmanager server is configured, instead of
+ failing to start.
+
## v0.94
### Added
diff --git a/cmd/karma/alerts.go b/cmd/karma/alerts.go
index be25d3329..ec98907ca 100644
--- a/cmd/karma/alerts.go
+++ b/cmd/karma/alerts.go
@@ -84,7 +84,9 @@ func countersToLabelStats(counters map[string]map[string]int) models.LabelNameSt
}
func getUpstreams() models.AlertmanagerAPISummary {
- summary := models.AlertmanagerAPISummary{}
+ summary := models.AlertmanagerAPISummary{
+ Instances: []models.AlertmanagerAPIStatus{},
+ }
clusters := map[string][]string{}
upstreams := alertmanager.GetAlertmanagers()
diff --git a/cmd/karma/main.go b/cmd/karma/main.go
index ff6e32b32..e6482871e 100644
--- a/cmd/karma/main.go
+++ b/cmd/karma/main.go
@@ -254,6 +254,10 @@ func lvlFormatter(level interface{}) string {
return fmt.Sprintf("level=%s", level)
}
+func discardFormatter(msg interface{}) string {
+ return ""
+}
+
func initLogger() {
log.Logger = log.Logger.Output(zerolog.ConsoleWriter{
Out: os.Stderr,
@@ -279,9 +283,19 @@ func setupLogger() error {
FormatMessage: msgFormatter,
TimeFormat: "15:04:05",
})
+ } else {
+ log.Logger = zerolog.New(os.Stderr).Output(zerolog.ConsoleWriter{
+ Out: os.Stderr,
+ NoColor: true,
+ FormatLevel: lvlFormatter,
+ FormatMessage: msgFormatter,
+ FormatTimestamp: discardFormatter,
+ })
}
case "json":
- if !config.Config.Log.Timestamp {
+ if config.Config.Log.Timestamp {
+ log.Logger = zerolog.New(os.Stderr).With().Timestamp().Logger()
+ } else {
log.Logger = zerolog.New(os.Stderr).With().Logger()
}
default:
@@ -382,10 +396,6 @@ func mainSetup(errorHandling pflag.ErrorHandling) (*chi.Mux, *historyPoller, err
return nil, nil, err
}
- if len(alertmanager.GetAlertmanagers()) == 0 {
- return nil, nil, fmt.Errorf("no valid Alertmanager URIs defined")
- }
-
if config.Config.Authorization.ACL.Silences != "" {
log.Info().
Str("path", config.Config.Authorization.ACL.Silences).
diff --git a/cmd/karma/tests/testscript/058_log_format_json.txt b/cmd/karma/tests/testscript/058_log_format_json.txt
index c7a48b594..bd64b5080 100644
--- a/cmd/karma/tests/testscript/058_log_format_json.txt
+++ b/cmd/karma/tests/testscript/058_log_format_json.txt
@@ -1,8 +1,22 @@
# Logs messages as JSON when log.format=json is passed
-karma.bin-should-fail --log.format=json --log.timestamp=false
+
+exec bash -x ./test.sh &
+karma.bin-should-work --log.format=json --log.timestamp=false --pid-file=karma.pid --listen.address=127.0.0.1 --listen.port=8058
! stdout .
cmp stderr stderr.txt
-- stderr.txt --
{"level":"info","message":"Version: dev"}
-{"level":"error","error":"no valid Alertmanager URIs defined","message":"Execution failed"}
+{"level":"info","path":"karma.pid","message":"Writing PID file"}
+{"level":"info","message":"Initial Alertmanager collection"}
+{"level":"info","message":"Pulling latest alerts and silences from Alertmanager"}
+{"level":"info","message":"Collection completed"}
+{"level":"info","message":"Done, starting HTTP server"}
+{"level":"info","address":"127.0.0.1:8058","message":"Starting HTTP server"}
+{"level":"info","message":"Shutting down HTTP server"}
+{"level":"info","message":"HTTP server shut down"}
+{"level":"info","path":"karma.pid","message":"Removing PID file"}
+-- test.sh --
+while [ ! -f karma.pid ]; do sleep 1 ; done
+sleep 5
+cat karma.pid | xargs kill
diff --git a/cmd/karma/tests/testscript/062_log_timestamp.txt b/cmd/karma/tests/testscript/062_log_timestamp.txt
index 8317bf4f1..957654c62 100644
--- a/cmd/karma/tests/testscript/062_log_timestamp.txt
+++ b/cmd/karma/tests/testscript/062_log_timestamp.txt
@@ -1,5 +1,12 @@
# Logs timestamps when log.timestamp is set to true
-karma.bin-should-fail --log.timestamp=true
+
+exec bash -x ./test.sh &
+karma.bin-should-work --log.timestamp=true --pid-file=karma.pid --listen.address=127.0.0.1 --listen.port=8062
! stdout .
stderr '[0-9][0-9]:[0-9][0-9]:[0-9][0-9] level=info msg="Version: dev"'
-stderr '[0-9][0-9]:[0-9][0-9]:[0-9][0-9] level=error msg="Execution failed" error="no valid Alertmanager URIs defined"'
+stderr '[0-9][0-9]:[0-9][0-9]:[0-9][0-9] level=info msg="Pulling latest alerts and silences from Alertmanager"'
+
+-- test.sh --
+while [ ! -f karma.pid ]; do sleep 1 ; done
+sleep 5
+cat karma.pid | xargs kill
diff --git a/cmd/karma/tests/testscript/064_log_timestamp_json.txt b/cmd/karma/tests/testscript/064_log_timestamp_json.txt
new file mode 100644
index 000000000..1666073a5
--- /dev/null
+++ b/cmd/karma/tests/testscript/064_log_timestamp_json.txt
@@ -0,0 +1,12 @@
+# Logs messages as JSON when log.format=json & log.timestamp=true is passed
+
+exec bash -x ./test.sh &
+karma.bin-should-work --log.format=json --log.timestamp=true --pid-file=karma.pid --listen.address=127.0.0.1 --listen.port=8064
+! stdout .
+stderr '{"level":"info","time":"[0-9]+-[0-9]+-[0-9]+T[0-9]+:[0-9]+:[0-9]+Z","message":"Version: dev"}'
+stderr '{"level":"info","time":"[0-9]+-[0-9]+-[0-9]+T[0-9]+:[0-9]+:[0-9]+Z","message":"Pulling latest alerts and silences from Alertmanager"}'
+
+-- test.sh --
+while [ ! -f karma.pid ]; do sleep 1 ; done
+sleep 5
+cat karma.pid | xargs kill
diff --git a/cmd/karma/tests/testscript/064_no_args.txt b/cmd/karma/tests/testscript/064_no_args.txt
deleted file mode 100644
index 1f4c46ebd..000000000
--- a/cmd/karma/tests/testscript/064_no_args.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-# Raises an error if no alertmanager uri is set
-karma.bin-should-fail
-! stdout .
-cmp stderr stderr.txt
-
--- stderr.txt --
-level=info msg="Version: dev"
-level=error msg="Execution failed" error="no valid Alertmanager URIs defined"
diff --git a/ui/src/Components/Grid/NoUpstream/__snapshots__/index.test.tsx.snap b/ui/src/Components/Grid/NoUpstream/__snapshots__/index.test.tsx.snap
new file mode 100644
index 000000000..a193908c5
--- /dev/null
+++ b/ui/src/Components/Grid/NoUpstream/__snapshots__/index.test.tsx.snap
@@ -0,0 +1,9 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[` matches snapshot 1`] = `
+"
+
+ No alertmanager server configured
+
+"
+`;
diff --git a/ui/src/Components/Grid/NoUpstream/index.test.tsx b/ui/src/Components/Grid/NoUpstream/index.test.tsx
new file mode 100644
index 000000000..de3f2ce8c
--- /dev/null
+++ b/ui/src/Components/Grid/NoUpstream/index.test.tsx
@@ -0,0 +1,19 @@
+import React from "react";
+
+import { shallow } from "enzyme";
+
+import toDiffableHtml from "diffable-html";
+
+import { MockThemeContext } from "__fixtures__/Theme";
+import { NoUpstream } from ".";
+
+beforeEach(() => {
+ jest.spyOn(React, "useContext").mockImplementation(() => MockThemeContext);
+});
+
+describe("", () => {
+ it("matches snapshot", () => {
+ const tree = shallow();
+ expect(toDiffableHtml(tree.html())).toMatchSnapshot();
+ });
+});
diff --git a/ui/src/Components/Grid/NoUpstream/index.tsx b/ui/src/Components/Grid/NoUpstream/index.tsx
new file mode 100644
index 000000000..dccc97ff4
--- /dev/null
+++ b/ui/src/Components/Grid/NoUpstream/index.tsx
@@ -0,0 +1,9 @@
+import type { FC } from "react";
+
+import { CenteredMessage } from "Components/CenteredMessage";
+
+const NoUpstream: FC = () => (
+ No alertmanager server configured
+);
+
+export { NoUpstream };
diff --git a/ui/src/Components/Grid/index.test.tsx b/ui/src/Components/Grid/index.test.tsx
index b116929a6..3947c6dff 100644
--- a/ui/src/Components/Grid/index.test.tsx
+++ b/ui/src/Components/Grid/index.test.tsx
@@ -131,10 +131,40 @@ describe("", () => {
it("renders EmptyGrid after first fetch when totalAlerts is 0", () => {
alertStore.info.setVersion("1.2.3");
alertStore.info.setTotalAlerts(0);
+ alertStore.data.setUpstreams({
+ counters: { total: 1, healthy: 1, failed: 1 },
+ instances: [
+ {
+ name: "dev",
+ cluster: "dev",
+ clusterMembers: ["dev"],
+ uri: "https://am.example.com",
+ publicURI: "https://am.example.com",
+ error: "",
+ readonly: false,
+ headers: {},
+ corsCredentials: "include",
+ version: "",
+ },
+ ],
+ clusters: { dev: ["dev"] },
+ });
const tree = ShallowGrid();
expect(tree.text()).toBe("");
});
+ it("renders NoUpstream after first fetch when upstream list is empty", () => {
+ alertStore.info.setVersion("1.2.3");
+ alertStore.info.setTotalAlerts(0);
+ alertStore.data.setUpstreams({
+ counters: { total: 0, healthy: 0, failed: 0 },
+ instances: [],
+ clusters: {},
+ });
+ const tree = ShallowGrid();
+ expect(tree.text()).toBe("");
+ });
+
it("renders AlertGrid after first fetch finished when totalAlerts is >0", () => {
alertStore.info.setVersion("unknown");
alertStore.info.setTotalAlerts(1);
diff --git a/ui/src/Components/Grid/index.tsx b/ui/src/Components/Grid/index.tsx
index 8011e6d5a..9b159fab0 100644
--- a/ui/src/Components/Grid/index.tsx
+++ b/ui/src/Components/Grid/index.tsx
@@ -10,6 +10,7 @@ import { FatalError } from "./FatalError";
import { UpgradeNeeded } from "./UpgradeNeeded";
import { ReloadNeeded } from "./ReloadNeeded";
import { EmptyGrid } from "./EmptyGrid";
+import { NoUpstream } from "./NoUpstream";
const Grid: FC<{
alertStore: AlertStore;
@@ -30,7 +31,11 @@ const Grid: FC<{
) : alertStore.info.version !== "unknown" &&
alertStore.info.totalAlerts === 0 ? (
-
+ alertStore.data.upstreams.instances.length === 0 ? (
+
+ ) : (
+
+ )
) : (
{
@@ -116,6 +121,38 @@ describe("", () => {
expect((mountModal.props() as any).onExited).toBe(onExited);
});
+ it("uses components-animation-modal class when animations are enabled", () => {
+ const onExited = jest.fn();
+ const tree = mount(
+
+
+ ,
+ {
+ wrappingComponent: ThemeContext.Provider,
+ wrappingComponentProps: { value: MockThemeContext },
+ }
+ );
+ const mountModal = tree.find("CSSTransition").at(0);
+ expect((mountModal.props() as any).classNames).toBe(
+ "components-animation-modal"
+ );
+ });
+
+ it("doesn't use components-animation-modal class when animations are disabled", () => {
+ const onExited = jest.fn();
+ const tree = mount(
+
+
+ ,
+ {
+ wrappingComponent: ThemeContext.Provider,
+ wrappingComponentProps: { value: MockThemeContextWithoutAnimations },
+ }
+ );
+ const mountModal = tree.find("CSSTransition").at(0);
+ expect((mountModal.props() as any).classNames).toBe("");
+ });
+
it("toggleOpen is called after pressing 'esc'", () => {
MountedModal(true);
PressKey("Escape", 27);
diff --git a/ui/src/Components/NavBar/index.stories.tsx b/ui/src/Components/NavBar/index.stories.tsx
index 9c5611206..2a83d8e7c 100644
--- a/ui/src/Components/NavBar/index.stories.tsx
+++ b/ui/src/Components/NavBar/index.stories.tsx
@@ -36,6 +36,24 @@ storiesOf("NavBar", module).add("NavBar", () => {
const settingsStore = new Settings(null);
const silenceFormStore = new SilenceFormStore();
+ alertStore.data.setUpstreams({
+ counters: { total: 1, healthy: 1, failed: 0 },
+ instances: [
+ {
+ name: "dev",
+ cluster: "dev",
+ clusterMembers: ["dev"],
+ uri: "https://am.example.com",
+ publicURI: "https://am.example.com",
+ error: "",
+ readonly: false,
+ headers: {},
+ corsCredentials: "include",
+ version: "",
+ },
+ ],
+ clusters: { dev: ["dev"] },
+ });
alertStore.info.setTotalAlerts(197);
alertStore.data.setColors({
cluster: {
diff --git a/ui/src/Components/NavBar/index.test.tsx b/ui/src/Components/NavBar/index.test.tsx
index 06dec4870..87e08d026 100644
--- a/ui/src/Components/NavBar/index.test.tsx
+++ b/ui/src/Components/NavBar/index.test.tsx
@@ -40,6 +40,25 @@ beforeEach(() => {
cb(0);
return 0;
});
+
+ alertStore.data.setUpstreams({
+ counters: { total: 1, healthy: 1, failed: 0 },
+ instances: [
+ {
+ name: "dev",
+ cluster: "dev",
+ clusterMembers: ["dev"],
+ uri: "https://am.example.com",
+ publicURI: "https://am.example.com",
+ error: "",
+ readonly: false,
+ headers: {},
+ corsCredentials: "include",
+ version: "",
+ },
+ ],
+ clusters: { dev: ["dev"] },
+ });
});
afterEach(() => {
@@ -64,6 +83,17 @@ const MountedNavbar = (fixedTop?: boolean) => {
};
describe("", () => {
+ it("renders null with no upstreams", () => {
+ alertStore.data.setUpstreams({
+ counters: { total: 0, healthy: 0, failed: 0 },
+ instances: [],
+ clusters: {},
+ });
+ alertStore.info.setTotalAlerts(15);
+ const tree = MountedNavbar();
+ expect(tree.find("span.navbar-brand")).toHaveLength(0);
+ });
+
it("navbar-brand shows 15 alerts with totalAlerts=15", () => {
alertStore.info.setTotalAlerts(15);
const tree = MountedNavbar();
diff --git a/ui/src/Components/NavBar/index.tsx b/ui/src/Components/NavBar/index.tsx
index 7c2c7269b..14ed86821 100644
--- a/ui/src/Components/NavBar/index.tsx
+++ b/ui/src/Components/NavBar/index.tsx
@@ -117,21 +117,33 @@ const NavBar: FC<{
} align-items-start`}
>
-
-
-
+ {alertStore.data.upstreams.instances.length === 0 ? null : (
+
+
+
+ )}
-
-
+ )}
+ {alertStore.data.upstreams.instances.length === 0 ? null : (
+
+ )}