diff --git a/dsc/Cargo.lock b/dsc/Cargo.lock index 18413a3a..334458ad 100644 --- a/dsc/Cargo.lock +++ b/dsc/Cargo.lock @@ -500,6 +500,7 @@ dependencies = [ "base64", "cc", "chrono", + "clap", "derive_builder", "indicatif", "jsonschema", diff --git a/dsc/assertion.dsc.resource.json b/dsc/assertion.dsc.resource.json index b567bad2..89578779 100644 --- a/dsc/assertion.dsc.resource.json +++ b/dsc/assertion.dsc.resource.json @@ -7,6 +7,8 @@ "get": { "executable": "dsc", "args": [ + "--trace-format", + "pass-through", "config", "--as-group", "test", @@ -17,6 +19,8 @@ "set": { "executable": "dsc", "args": [ + "--trace-format", + "pass-through", "config", "--as-group", "test" @@ -28,6 +32,8 @@ "test": { "executable": "dsc", "args": [ + "--trace-format", + "pass-through", "config", "--as-group", "test", @@ -45,19 +51,11 @@ "5": "Resource instance failed schema validation", "6": "Command cancelled" }, - "schema": { - "command": { - "executable": "dsc", - "args": [ - "schema", - "--type", - "configuration" - ] - } - }, "validate": { "executable": "dsc", "args": [ + "--trace-format", + "pass-through", "config", "validate" ], diff --git a/dsc/group.dsc.resource.json b/dsc/group.dsc.resource.json index b71923b8..babfd82c 100644 --- a/dsc/group.dsc.resource.json +++ b/dsc/group.dsc.resource.json @@ -7,6 +7,8 @@ "get": { "executable": "dsc", "args": [ + "--trace-format", + "pass-through", "config", "--as-group", "get" @@ -16,6 +18,8 @@ "set": { "executable": "dsc", "args": [ + "--trace-format", + "pass-through", "config", "--as-group", "set" @@ -27,6 +31,8 @@ "test": { "executable": "dsc", "args": [ + "--trace-format", + "pass-through", "config", "--as-group", "test" @@ -46,6 +52,8 @@ "validate": { "executable": "dsc", "args": [ + "--trace-format", + "pass-through", "config", "validate" ], diff --git a/dsc/include.dsc.resource.json b/dsc/include.dsc.resource.json index 85c99da0..62639a06 100644 --- a/dsc/include.dsc.resource.json +++ b/dsc/include.dsc.resource.json @@ -7,6 +7,8 @@ "get": { "executable": "dsc", "args": [ + "--trace-format", + "pass-through", "config", "--as-include", "--as-group", @@ -17,6 +19,8 @@ "set": { "executable": "dsc", "args": [ + "--trace-format", + "pass-through", "config", "--as-include", "--as-group", @@ -29,6 +33,8 @@ "test": { "executable": "dsc", "args": [ + "--trace-format", + "pass-through", "config", "--as-include", "--as-group", @@ -48,6 +54,8 @@ "validate": { "executable": "dsc", "args": [ + "--trace-format", + "pass-through", "config", "--as-include", "validate" diff --git a/dsc/parallel.dsc.resource.json b/dsc/parallel.dsc.resource.json index d3de7f61..dc9172c7 100644 --- a/dsc/parallel.dsc.resource.json +++ b/dsc/parallel.dsc.resource.json @@ -7,6 +7,8 @@ "get": { "executable": "dsc", "args": [ + "--trace-format", + "pass-through", "config", "--parallel", "--as-group", @@ -17,6 +19,8 @@ "set": { "executable": "dsc", "args": [ + "--trace-format", + "pass-through", "config", "--parallel", "--as-group", @@ -29,6 +33,8 @@ "test": { "executable": "dsc", "args": [ + "--trace-format", + "pass-through", "config", "--parallel", "--as-group", @@ -49,6 +55,8 @@ "validate": { "executable": "dsc", "args": [ + "--trace-format", + "pass-through", "config", "validate" ], diff --git a/dsc/src/args.rs b/dsc/src/args.rs index cf1fc78d..e572ca31 100644 --- a/dsc/src/args.rs +++ b/dsc/src/args.rs @@ -3,6 +3,7 @@ use clap::{Parser, Subcommand, ValueEnum}; use clap_complete::Shell; +use dsc_lib::dscresources::command_resource::TraceLevel; #[derive(Debug, Clone, PartialEq, Eq, ValueEnum)] pub enum OutputFormat { @@ -16,15 +17,8 @@ pub enum TraceFormat { Default, Plaintext, Json, -} - -#[derive(Debug, Clone, PartialEq, Eq, ValueEnum)] -pub enum TraceLevel { - Error, - Warn, - Info, - Debug, - Trace + #[clap(hide = true)] + PassThrough, } #[derive(Debug, Parser)] diff --git a/dsc/src/tablewriter.rs b/dsc/src/tablewriter.rs index 5c8c5d00..de4fe9c5 100644 --- a/dsc/src/tablewriter.rs +++ b/dsc/src/tablewriter.rs @@ -1,4 +1,4 @@ -use crossterm::terminal::{size}; +use crossterm::terminal::size; pub struct Table { header: Vec, @@ -8,13 +8,13 @@ pub struct Table { impl Table { /// Create a new table. - /// + /// /// # Arguments - /// + /// /// * `header` - The header row - /// + /// /// # Returns - /// + /// /// * `Table` - The new table #[must_use] pub fn new(header: &[&str]) -> Table { @@ -31,9 +31,9 @@ impl Table { } /// Add a row to the table. - /// + /// /// # Arguments - /// + /// /// * `row` - The row to add pub fn add_row(&mut self, row: Vec) { for (i, column) in row.iter().enumerate() { diff --git a/dsc/src/util.rs b/dsc/src/util.rs index 8a160986..ab337564 100644 --- a/dsc/src/util.rs +++ b/dsc/src/util.rs @@ -1,8 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::args::{DscType, OutputFormat, TraceFormat, TraceLevel}; - +use crate::args::{DscType, OutputFormat, TraceFormat}; use atty::Stream; use crate::resolve::Include; use dsc_lib::{ @@ -16,6 +15,7 @@ use dsc_lib::{ }, dscerror::DscError, dscresources::{ + command_resource::TraceLevel, dscresource::DscResource, invoke_result::{ GetResult, SetResult, @@ -323,7 +323,7 @@ pub fn enable_tracing(trace_level: &Option, trace_format: &TraceForm .with_line_number(with_source) .boxed() }, - TraceFormat::Json => { + TraceFormat::Json | TraceFormat::PassThrough => { layer .with_ansi(false) .with_level(true) @@ -331,7 +331,7 @@ pub fn enable_tracing(trace_level: &Option, trace_format: &TraceForm .with_line_number(with_source) .json() .boxed() - } + }, }; let subscriber = tracing_subscriber::Registry::default().with(fmt).with(filter).with(indicatif_layer); diff --git a/dsc/tests/dsc_tracing.tests.ps1 b/dsc/tests/dsc_tracing.tests.ps1 index 56698ac0..23355531 100644 --- a/dsc/tests/dsc_tracing.tests.ps1 +++ b/dsc/tests/dsc_tracing.tests.ps1 @@ -85,4 +85,16 @@ Describe 'tracing tests' { $out = (dsc -l $level config get -d $configYaml 2> $null) | ConvertFrom-Json $out.results[0].result.actualState.level | Should -BeExactly $level } + + It 'Pass-through tracing should only emit JSON for child processes' { + $logPath = "$TestDrive/dsc_trace.log" + $out = dsc -l info -f pass-through config get -p ../examples/groups.dsc.yaml 2> $logPath + foreach ($line in (Get-Content $logPath)) { + $line | Should -Not -BeNullOrEmpty + $json = $line | ConvertFrom-Json + $json.timestamp | Should -Not -BeNullOrEmpty + $json.level | Should -BeIn 'ERROR', 'WARN', 'INFO', 'DEBUG', 'TRACE' + } + $out | Should -BeNullOrEmpty + } } diff --git a/dsc_lib/Cargo.lock b/dsc_lib/Cargo.lock index f0ec407a..e32bf324 100644 --- a/dsc_lib/Cargo.lock +++ b/dsc_lib/Cargo.lock @@ -370,6 +370,7 @@ dependencies = [ "base64", "cc", "chrono", + "clap", "derive_builder", "indicatif", "jsonschema", diff --git a/dsc_lib/Cargo.toml b/dsc_lib/Cargo.toml index 1cf0610a..cd8244f4 100644 --- a/dsc_lib/Cargo.toml +++ b/dsc_lib/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] base64 = "0.22.1" chrono = "0.4.26" +clap = { version = "4.4", features = ["derive"] } derive_builder ="0.20.0" indicatif = "0.17.0" jsonschema = "0.18.0" diff --git a/dsc_lib/src/dscresources/command_resource.rs b/dsc_lib/src/dscresources/command_resource.rs index 30a44952..c3c86c46 100644 --- a/dsc_lib/src/dscresources/command_resource.rs +++ b/dsc_lib/src/dscresources/command_resource.rs @@ -1,7 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +use clap::ValueEnum; use jsonschema::JSONSchema; +use serde::Deserialize; use serde_json::Value; use std::{collections::HashMap, env, process::Stdio}; use crate::configure::{config_doc::ExecutionKind, config_result::{ResourceGetResult, ResourceTestResult}}; @@ -815,26 +817,94 @@ pub fn log_stderr_line<'a>(process_id: &u32, trace_line: &'a str) -> &'a str { if !trace_line.is_empty() { - if let Result::Ok(json_obj) = serde_json::from_str::(trace_line) { + if let Ok(trace_object) = serde_json::from_str::(trace_line) { + let mut include_target = trace_object.level == TraceLevel::Debug || trace_object.level == TraceLevel::Trace; + let target = if let Some(t) = trace_object.target.as_deref() { t } else { + include_target = false; + "" + }; + let line_number = if let Some(l) = trace_object.line_number { l } else { + include_target = false; + 0 + }; + let trace_message = if include_target { + format!("PID {process_id}: {target}: {line_number}: {}", trace_object.fields.message) + } else { + format!("PID {process_id}: {}", trace_object.fields.message) + }; + match trace_object.level { + TraceLevel::Error => { + error!(trace_message); + }, + TraceLevel::Warn => { + warn!(trace_message); + }, + TraceLevel::Info => { + info!(trace_message); + }, + TraceLevel::Debug => { + debug!(trace_message); + }, + TraceLevel::Trace => { + trace!(trace_message); + }, + } + } + else if let Ok(json_obj) = serde_json::from_str::(trace_line) { if let Some(msg) = json_obj.get("Error") { - error!("Process id {process_id} : {}", msg.as_str().unwrap_or_default()); + error!("PID {process_id}: {}", msg.as_str().unwrap_or_default()); } else if let Some(msg) = json_obj.get("Warning") { - warn!("Process id {process_id} : {}", msg.as_str().unwrap_or_default()); + warn!("PID {process_id}: {}", msg.as_str().unwrap_or_default()); } else if let Some(msg) = json_obj.get("Info") { - info!("Process id {process_id} : {}", msg.as_str().unwrap_or_default()); + info!("PID {process_id}: {}", msg.as_str().unwrap_or_default()); } else if let Some(msg) = json_obj.get("Debug") { - debug!("Process id {process_id} : {}", msg.as_str().unwrap_or_default()); + debug!("PID {process_id}: {}", msg.as_str().unwrap_or_default()); } else if let Some(msg) = json_obj.get("Trace") { - trace!("Process id {process_id} : {}", msg.as_str().unwrap_or_default()); + trace!("PID {process_id}: {}", msg.as_str().unwrap_or_default()); } else { // the line is a valid json, but not one of standard trace lines - return it as filtered stderr_line + trace!("PID {process_id}: {trace_line}"); return trace_line; }; } else { // the line is not a valid json - return it as filtered stderr_line + trace!("PID {process_id}: {}", trace_line); return trace_line; } }; "" } + +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, ValueEnum)] +pub enum TraceLevel { + #[serde(rename = "ERROR")] + Error, + #[serde(rename = "WARN")] + Warn, + #[serde(rename = "INFO")] + Info, + #[serde(rename = "DEBUG")] + Debug, + #[serde(rename = "TRACE")] + Trace, +} + +#[derive(Deserialize)] +struct Fields { + message: String, +} + +#[derive(Deserialize)] +struct Trace { + #[serde(rename = "timestamp")] + _timestamp: String, + level: TraceLevel, + fields: Fields, + target: Option, + line_number: Option, + #[serde(rename = "span")] + _span: Option>, + #[serde(rename = "spans")] + _spans: Option>>, +} diff --git a/runcommandonset/RunCommandOnSet.dsc.resource.json b/runcommandonset/RunCommandOnSet.dsc.resource.json index 5f60f353..121f8677 100644 --- a/runcommandonset/RunCommandOnSet.dsc.resource.json +++ b/runcommandonset/RunCommandOnSet.dsc.resource.json @@ -6,6 +6,8 @@ "get": { "executable": "runcommandonset", "args": [ + "--trace-format", + "json", "get" ], "input": "stdin" @@ -13,6 +15,8 @@ "set": { "executable": "runcommandonset", "args": [ + "--trace-format", + "json", "set" ], "input": "stdin", diff --git a/tools/test_group_resource/Cargo.lock b/tools/test_group_resource/Cargo.lock index 9175443e..e6e87d29 100644 --- a/tools/test_group_resource/Cargo.lock +++ b/tools/test_group_resource/Cargo.lock @@ -369,6 +369,7 @@ dependencies = [ "base64", "cc", "chrono", + "clap", "derive_builder", "indicatif", "jsonschema", diff --git a/tree-sitter-dscexpression/build.ps1 b/tree-sitter-dscexpression/build.ps1 index 11928d72..ce941041 100644 --- a/tree-sitter-dscexpression/build.ps1 +++ b/tree-sitter-dscexpression/build.ps1 @@ -29,7 +29,12 @@ if ($null -eq (Get-Command npm -ErrorAction Ignore)) { } } -npm ci --omit:optional --force --registry https://pkgs.dev.azure.com/powershell/PowerShell/_packaging/powershell/npm/registry/ +if ($null -ne $env:TF_BUILD) { + npm ci --omit:optional --registry https://pkgs.dev.azure.com/powershell/PowerShell/_packaging/powershell/npm/registry/ +} +else { + npm install --omit:optional --registry https://pkgs.dev.azure.com/powershell/PowerShell/_packaging/powershell/npm/registry/ +} #npm list tree-sitter-cli #if ($LASTEXITCODE -ne 0) {