Add the ability to enable debug logs in the config entry overflow (#14319)

This commit is contained in:
J. Nick Koston 2022-12-01 05:53:41 -10:00 committed by GitHub
parent b97a9ef311
commit 44502d2c8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 156 additions and 2 deletions

View File

@ -1,5 +1,7 @@
import { Connection, createCollection } from "home-assistant-js-websocket";
import { LocalizeFunc } from "../common/translations/localize"; import { LocalizeFunc } from "../common/translations/localize";
import { HomeAssistant } from "../types"; import { HomeAssistant } from "../types";
import { debounce } from "../common/util/debounce";
export type IntegrationType = export type IntegrationType =
| "device" | "device"
@ -23,6 +25,7 @@ export interface IntegrationManifest {
zeroconf?: string[]; zeroconf?: string[];
homekit?: { models: string[] }; homekit?: { models: string[] };
integration_type?: IntegrationType; integration_type?: IntegrationType;
loggers?: string[];
quality_scale?: "gold" | "internal" | "platinum" | "silver"; quality_scale?: "gold" | "internal" | "platinum" | "silver";
iot_class: iot_class:
| "assumed_state" | "assumed_state"
@ -36,6 +39,24 @@ export interface IntegrationSetup {
seconds?: number; seconds?: number;
} }
export interface IntegrationLogInfo {
domain: string;
level?: number;
}
export enum LogSeverity {
CRITICAL = 50,
FATAL = 50,
ERROR = 40,
WARNING = 30,
WARN = 30,
INFO = 20,
DEBUG = 10,
NOTSET = 0,
}
export type IntegrationLogPersistance = "none" | "once" | "permanent";
export const integrationIssuesUrl = ( export const integrationIssuesUrl = (
domain: string, domain: string,
manifest: IntegrationManifest manifest: IntegrationManifest
@ -69,3 +90,46 @@ export const fetchIntegrationManifest = (
export const fetchIntegrationSetups = (hass: HomeAssistant) => export const fetchIntegrationSetups = (hass: HomeAssistant) =>
hass.callWS<IntegrationSetup[]>({ type: "integration/setup_info" }); hass.callWS<IntegrationSetup[]>({ type: "integration/setup_info" });
export const fetchIntegrationLogInfo = (conn) =>
conn.sendMessagePromise({
type: "logger/log_info",
});
export const setIntegrationLogLevel = (
hass: HomeAssistant,
integration: string,
level: string,
persistence: IntegrationLogPersistance
) =>
hass.callWS({
type: "logger/integration_log_level",
integration,
level,
persistence,
});
const subscribeLogInfoUpdates = (conn, store) =>
conn.subscribeEvents(
debounce(
() =>
fetchIntegrationLogInfo(conn).then((log_infos) =>
store.setState(log_infos, true)
),
200,
true
),
"logging_changed"
);
export const subscribeLogInfo = (
conn: Connection,
onChange: (devices: IntegrationLogInfo[]) => void
) =>
createCollection<IntegrationLogInfo[]>(
"_integration_log_info",
fetchIntegrationLogInfo,
subscribeLogInfoUpdates,
conn,
onChange
);

View File

@ -51,6 +51,8 @@ import {
fetchIntegrationManifest, fetchIntegrationManifest,
fetchIntegrationManifests, fetchIntegrationManifests,
IntegrationManifest, IntegrationManifest,
IntegrationLogInfo,
subscribeLogInfo,
} from "../../../data/integration"; } from "../../../data/integration";
import { import {
getIntegrationDescriptions, getIntegrationDescriptions,
@ -154,6 +156,10 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
@state() private _diagnosticHandlers?: Record<string, boolean>; @state() private _diagnosticHandlers?: Record<string, boolean>;
@state() private _logInfos?: {
[integration: string]: IntegrationLogInfo;
};
public hassSubscribe(): Array<UnsubscribeFunc | Promise<UnsubscribeFunc>> { public hassSubscribe(): Array<UnsubscribeFunc | Promise<UnsubscribeFunc>> {
return [ return [
subscribeEntityRegistry(this.hass.connection, (entries) => { subscribeEntityRegistry(this.hass.connection, (entries) => {
@ -230,6 +236,13 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
}, },
{ type: ["device", "hub", "service"] } { type: ["device", "hub", "service"] }
), ),
subscribeLogInfo(this.hass.connection, (log_infos) => {
const logInfoLookup: { [integration: string]: IntegrationLogInfo } = {};
for (const log_info of log_infos) {
logInfoLookup[log_info.domain] = log_info;
}
this._logInfos = logInfoLookup;
}),
]; ];
} }
@ -514,6 +527,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
.supportsDiagnostics=${this._diagnosticHandlers .supportsDiagnostics=${this._diagnosticHandlers
? this._diagnosticHandlers[domain] ? this._diagnosticHandlers[domain]
: false} : false}
.logInfo=${this._logInfos ? this._logInfos[domain] : null}
></ha-integration-card>` ></ha-integration-card>`
) )
: this._filter && : this._filter &&

View File

@ -5,6 +5,8 @@ import {
mdiAlertCircle, mdiAlertCircle,
mdiBookshelf, mdiBookshelf,
mdiBug, mdiBug,
mdiBugPlay,
mdiBugStop,
mdiChevronLeft, mdiChevronLeft,
mdiCog, mdiCog,
mdiDelete, mdiDelete,
@ -47,11 +49,17 @@ import {
ERROR_STATES, ERROR_STATES,
RECOVERABLE_STATES, RECOVERABLE_STATES,
} from "../../../data/config_entries"; } from "../../../data/config_entries";
import { getErrorLogDownloadUrl } from "../../../data/error_log";
import type { DeviceRegistryEntry } from "../../../data/device_registry"; import type { DeviceRegistryEntry } from "../../../data/device_registry";
import { getConfigEntryDiagnosticsDownloadUrl } from "../../../data/diagnostics"; import { getConfigEntryDiagnosticsDownloadUrl } from "../../../data/diagnostics";
import type { EntityRegistryEntry } from "../../../data/entity_registry"; import type { EntityRegistryEntry } from "../../../data/entity_registry";
import type { IntegrationManifest } from "../../../data/integration"; import type { IntegrationManifest } from "../../../data/integration";
import { integrationIssuesUrl } from "../../../data/integration"; import {
integrationIssuesUrl,
IntegrationLogInfo,
LogSeverity,
setIntegrationLogLevel,
} from "../../../data/integration";
import { showConfigEntrySystemOptionsDialog } from "../../../dialogs/config-entry-system-options/show-dialog-config-entry-system-options"; import { showConfigEntrySystemOptionsDialog } from "../../../dialogs/config-entry-system-options/show-dialog-config-entry-system-options";
import { showOptionsFlowDialog } from "../../../dialogs/config-flow/show-dialog-options-flow"; import { showOptionsFlowDialog } from "../../../dialogs/config-flow/show-dialog-options-flow";
import { import {
@ -95,6 +103,8 @@ export class HaIntegrationCard extends LitElement {
@property({ type: Boolean }) public supportsDiagnostics = false; @property({ type: Boolean }) public supportsDiagnostics = false;
@property() public logInfo?: IntegrationLogInfo;
protected render(): TemplateResult { protected render(): TemplateResult {
let item = this._selectededConfigEntry; let item = this._selectededConfigEntry;
@ -137,6 +147,8 @@ export class HaIntegrationCard extends LitElement {
.localizedDomainName=${item ? item.localized_domain_name : undefined} .localizedDomainName=${item ? item.localized_domain_name : undefined}
.manifest=${this.manifest} .manifest=${this.manifest}
.configEntry=${item} .configEntry=${item}
.debugLoggingEnabled=${this.logInfo &&
this.logInfo.level === LogSeverity.DEBUG}
> >
${this.items.length > 1 ${this.items.length > 1
? html` ? html`
@ -398,6 +410,28 @@ export class HaIntegrationCard extends LitElement {
</mwc-list-item> </mwc-list-item>
</a>` </a>`
: ""} : ""}
${this.logInfo
? html`<mwc-list-item
@request-selected=${this.logInfo.level === LogSeverity.DEBUG
? this._handleDisableDebugLogging
: this._handleEnableDebugLogging}
graphic="icon"
>
${this.logInfo.level === LogSeverity.DEBUG
? this.hass.localize(
"ui.panel.config.integrations.config_entry.disable_debug_logging"
)
: this.hass.localize(
"ui.panel.config.integrations.config_entry.enable_debug_logging"
)}
<ha-svg-icon
slot="graphic"
.path=${this.logInfo.level === LogSeverity.DEBUG
? mdiBugStop
: mdiBugPlay}
></ha-svg-icon>
</mwc-list-item>`
: ""}
${this.manifest && ${this.manifest &&
(this.manifest.is_built_in || (this.manifest.is_built_in ||
this.manifest.issue_tracker || this.manifest.issue_tracker ||
@ -501,6 +535,34 @@ export class HaIntegrationCard extends LitElement {
`; `;
} }
private async _handleEnableDebugLogging(ev: MouseEvent) {
const configEntry = ((ev.target as HTMLElement).closest("ha-card") as any)
.configEntry;
const integration = configEntry.domain;
await setIntegrationLogLevel(
this.hass,
integration,
LogSeverity[LogSeverity.DEBUG],
"once"
);
}
private async _handleDisableDebugLogging(ev: MouseEvent) {
const configEntry = ((ev.target as HTMLElement).closest("ha-card") as any)
.configEntry;
const integration = configEntry.domain;
await setIntegrationLogLevel(
this.hass,
integration,
LogSeverity[LogSeverity.NOTSET],
"once"
);
const timeString = new Date().toISOString().replace(/:/g, "-");
const logFileName = `home-assistant_${integration}_${timeString}.log`;
const signedUrl = await getSignedPath(this.hass, getErrorLogDownloadUrl);
fileDownload(signedUrl.path, logFileName);
}
private get _selectededConfigEntry(): ConfigEntryExtended | undefined { private get _selectededConfigEntry(): ConfigEntryExtended | undefined {
return this.items.length === 1 return this.items.length === 1
? this.items[0] ? this.items[0]

View File

@ -1,4 +1,4 @@
import { mdiCloud, mdiPackageVariant, mdiSyncOff } from "@mdi/js"; import { mdiBugPlay, mdiCloud, mdiPackageVariant, mdiSyncOff } from "@mdi/js";
import "@polymer/paper-tooltip/paper-tooltip"; import "@polymer/paper-tooltip/paper-tooltip";
import { css, html, LitElement, TemplateResult } from "lit"; import { css, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
@ -24,6 +24,8 @@ export class HaIntegrationHeader extends LitElement {
@property({ attribute: false }) public configEntry?: ConfigEntry; @property({ attribute: false }) public configEntry?: ConfigEntry;
@property({ attribute: false }) public debugLoggingEnabled?: boolean;
protected render(): TemplateResult { protected render(): TemplateResult {
let primary: string; let primary: string;
let secondary: string | undefined; let secondary: string | undefined;
@ -76,6 +78,15 @@ export class HaIntegrationHeader extends LitElement {
} }
} }
if (this.debugLoggingEnabled) {
icons.push([
mdiBugPlay,
this.hass.localize(
"ui.panel.config.integrations.config_entry.debug_logging_enabled"
),
]);
}
return html` return html`
${!this.banner ? "" : html`<div class="banner">${this.banner}</div>`} ${!this.banner ? "" : html`<div class="banner">${this.banner}</div>`}
<slot name="above-header"></slot> <slot name="above-header"></slot>

View File

@ -3011,10 +3011,12 @@
"system_options": "System options", "system_options": "System options",
"documentation": "Documentation", "documentation": "Documentation",
"download_diagnostics": "Download diagnostics", "download_diagnostics": "Download diagnostics",
"disable_debug_logging": "Disable debug logging",
"known_issues": "Known issues", "known_issues": "Known issues",
"delete": "Delete", "delete": "Delete",
"delete_confirm_title": "Delete {title}?", "delete_confirm_title": "Delete {title}?",
"delete_confirm_text": "Its devices and entities will be permanently deleted.", "delete_confirm_text": "Its devices and entities will be permanently deleted.",
"enable_debug_logging": "Enable debug logging",
"reload": "Reload", "reload": "Reload",
"restart_confirm": "Restart Home Assistant to finish removing this integration", "restart_confirm": "Restart Home Assistant to finish removing this integration",
"reload_confirm": "The integration was reloaded", "reload_confirm": "The integration was reloaded",
@ -3049,6 +3051,7 @@
"depends_on_cloud": "Depends on the cloud", "depends_on_cloud": "Depends on the cloud",
"yaml_only": "Needs manual configuration", "yaml_only": "Needs manual configuration",
"disabled_polling": "Automatic polling for updated data disabled", "disabled_polling": "Automatic polling for updated data disabled",
"debug_logging_enabled": "Debug logging enabled",
"state": { "state": {
"loaded": "Loaded", "loaded": "Loaded",
"setup_error": "Failed to set up", "setup_error": "Failed to set up",