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
2 changes: 1 addition & 1 deletion .github/workflows/ci-rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ jobs:
key: ${{ runner.os }}-cargo-clippy-${{ hashFiles('**/Cargo.lock') }}

- name: Run Clang Format check for the C/C++ zypp bindings
run: make check
run: make check || (make format; echo "Suggested changes:"; git diff; exit 1)
working-directory: ./rust/zypp-agama/zypp-agama-sys/c-layer

- name: Run clippy linter
Expand Down
42 changes: 29 additions & 13 deletions rust/agama-lib/src/logs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,18 @@ use serde::Serialize;
use std::fs;
use std::fs::File;
use std::io;
use std::io::Write;
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};
use std::process::Command;
use tempfile::TempDir;
use utoipa::ToSchema;
use zypp_agama::SOLVER_TESTCASE_DIR;

const DEFAULT_COMMANDS: [(&str, &str); 2] = [
const DEFAULT_COMMANDS: [(&str, &str); 4] = [
// (<command to be executed>, <file name used for storing result of the command>)
("journalctl", "journald"),
("journalctl -o json", "journal_json"),
("agama-journal", "journal"),
("agama-zypp-journal", "libzypp"),
("rpm -qa", "rpm-qa"),
];

Expand Down Expand Up @@ -178,19 +179,34 @@ impl LogItem for LogCmd {
fn store(&self) -> Result<(), io::Error> {
let cmd_parts = self.cmd.split_whitespace().collect::<Vec<&str>>();
let file_path = self.to();
let output = Command::new(cmd_parts[0])
.args(cmd_parts[1..].iter())
.output()?;

if !output.stdout.is_empty() {
let mut file_stdout = File::create(format!("{}.out.log", file_path.display()))?;
let mut stdout_name = file_path.clone();
stdout_name.set_extension("out.log");

file_stdout.write_all(&output.stdout)?;
}
if !output.stderr.is_empty() {
let mut file_stderr = File::create(format!("{}.err.log", file_path.display()))?;
let mut stderr_name = file_path.clone();
stderr_name.set_extension("err.log");

let file_stdout = File::create(&stdout_name)?;
let file_stderr = File::create(&stderr_name)?;

let status = Command::new(cmd_parts[0])
.args(&cmd_parts[1..])
.stdout(file_stdout)
.stderr(file_stderr)
.status()?;

file_stderr.write_all(&output.stderr)?;
tracing::info!(
"Command {} finished with status: {}",
self.cmd,
status.code().unwrap_or_default()
);

// delete the created files if they are empty
if fs::metadata(&stdout_name)?.len() == 0 {
let _ = fs::remove_file(&stdout_name);
}
if fs::metadata(&stderr_name)?.len() == 0 {
let _ = fs::remove_file(&stderr_name);
}

Ok(())
Expand Down
2 changes: 2 additions & 0 deletions rust/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ install -D -t "${DESTDIR}${bindir}" "${SRCDIR}/target/${RUST_TARGET}/agama-autoi
install -D -t "${DESTDIR}${bindir}" "${SRCDIR}/target/${RUST_TARGET}/agama-proxy-setup"
install -D -t "${DESTDIR}${bindir}" "${SRCDIR}/target/${RUST_TARGET}/agama-web-server"
install -D -t "${DESTDIR}${bindir}" "${SRCDIR}/share/agama-web-server.sh"
install -D -t "${DESTDIR}${bindir}" "${SRCDIR}/share/agama-journal"
install -D -t "${DESTDIR}${bindir}" "${SRCDIR}/share/agama-zypp-journal"

install6 -D -p "${SRCDIR}"/share/agama.pam "${DESTDIR}${pamvendordir}"/agama

Expand Down
10 changes: 10 additions & 0 deletions rust/package/agama.changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
-------------------------------------------------------------------
Tue Mar 24 13:01:38 UTC 2026 - Ladislav Slezák <lslezak@suse.com>

- Include more details when saving the journal log
- Save separate libzypp.out.log file in the old y2log format
for libzypp debugging
- Store the complete journal log in JSON format to include hidden
fields and more details
- gh#agama-project/agama#3309

-------------------------------------------------------------------
Fri Mar 20 16:56:53 UTC 2026 - Imobach Gonzalez Sosa <igonzalezsosa@suse.com>

Expand Down
2 changes: 2 additions & 0 deletions rust/package/agama.spec
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,8 @@ echo $PATH
%license LICENSE
%{_bindir}/agama-web-server
%{_bindir}/agama-web-server.sh
%{_bindir}/agama-journal
%{_bindir}/agama-zypp-journal
%{_bindir}/agama-proxy-setup
%{_pam_vendordir}/agama
%{_unitdir}/agama-web-server.service
Expand Down
131 changes: 131 additions & 0 deletions rust/share/agama-journal
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
#!/bin/bash
#
# Copyright (c) [2026] SUSE LLC
#
# All Rights Reserved.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 2 of the License, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, contact SUSE LLC.
#
# To contact SUSE LLC about this file by physical or electronic mail, you may
# find current contact information at www.suse.com.

# Helper script which prints the messages from the systemd journal with
# additional details like code location.

# All script arguments are passed to journalctl, you can use it to filter the
# displayed messages, e.g. filter by unit with '-u sshd.service', filter by time
# with '--since "5 minutes ago"' or show only errors and higher priority with
# '-p err'..

# The timestamps are printed in the local time, to print the UTC time
# run with TZ=UTC. Hint: You can set any time zone, e.g. TZ=America/New_York.

# It accepts special "--input-file <file>" parameter to load the data from
# previously saved journal in JSON format.


# Use colors if the output goes to a terminal
if [ -t 1 ]; then
USE_COLORS="true"
else
USE_COLORS="false"
fi

SHOW_MICROSECONDS="false"
INPUT_FILE=""
JOURNAL_ARGS=()

while [ $# -gt 0 ]; do
case "$1" in
# Allow using a JSON file as input
--input-file)
INPUT_FILE="$2"
shift 2
;;
--microseconds)
SHOW_MICROSECONDS="true"
shift
;;
*)
JOURNAL_ARGS+=("$1")
shift
;;
esac
done

process_logs() {
if [ -n "$INPUT_FILE" ]; then
cat "$INPUT_FILE"
else
journalctl -o json "$@"
fi | jq --unbuffered --arg use_colors "$USE_COLORS" --arg show_microseconds "$SHOW_MICROSECONDS" -r '
# Helper function to get color based on priority
def get_color:
{
"0": "\u001b[1;91m", # Emergency - Bold Bright Red
"1": "\u001b[1;91m", # Alert - Bold Bright Red
"2": "\u001b[1;91m", # Critical - Bold Bright Red
"3": "\u001b[1;31m", # Error - Bold Red
"4": "\u001b[93m", # Warning - Bright Yellow
"5": "\u001b[1m", # Notice - Bold white
"6": "", # Informational - Default
"7": "\u001b[90m" # Debug - Grey
}[tostring] // "";

# Helper function to format timestamp
def format_timestamp:
# The journal time is in microseconds
(tonumber / 1000000 | strflocaltime("%b %e %H:%M:%S"))
+ (if $show_microseconds == "true" then "." + (tostring | .[-6:]) else "" end);

# Helper function to build the source code location string
def format_location:
[
(.CODE_FILE | select(. != null and . != "") | tostring),
(.CODE_FUNC | select(. != null and . != "") | "(" + tostring + ")"),
(.CODE_LINE | select(. != null and . != "") | ":" + tostring + ":")
]
| join("")
| select(. != "");

# Helper function to combine syslog identifier and PID
def format_syslog_pid:
[
(.SYSLOG_IDENTIFIER | select(. != null and . != "")),
(._PID | select(. != null and . != "") | "[" + tostring + "]:")
]
| join("")
| select(. != "");

# Join the requested fields into a single line
(.PRIORITY | get_color) as $color |
[
(.__REALTIME_TIMESTAMP | select(. != null and . != "") | format_timestamp),
(.PRIORITY | select(. != null and . != "") | "<" + tostring + ">"),
format_syslog_pid,
format_location,
(.ZYPP_GROUP | select(. != null and . != "") | "[" + tostring + "]"),
(.MESSAGE | select(. != null and . != "") | tostring | if $use_colors == "true" and $color != "" then $color + . + "\u001b[0m" else . end)
]
# Remove any missing/empty values and join them with a space
| map(select(. != null and . != ""))
| join(" ")
'
}

if [ "$USE_COLORS" = "true" ] && [ -n "$PAGER" ]; then
process_logs "${JOURNAL_ARGS[@]}" | $PAGER
else
process_logs "${JOURNAL_ARGS[@]}"
fi
119 changes: 119 additions & 0 deletions rust/share/agama-zypp-journal
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
#!/bin/bash
#
# Copyright (c) [2026] SUSE LLC
#
# All Rights Reserved.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 2 of the License, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, contact SUSE LLC.
#
# To contact SUSE LLC about this file by physical or electronic mail, you may
# find current contact information at www.suse.com.

# Helper script which extracts the libzypp messages from the systemd journal
# and prints them in the YaST y2log compatible format.

# All script arguments are passed to journalctl, you can use it to filter the
# displayed messages, e.g. by date with '--since "5 minutes ago"' or show only
# errors and higher priority with '-p err'.

# It accepts special "--input-file <file>" parameter to load the data from
# previously saved journal in JSON format.


# Use colors if the output goes to a terminal
if [ -t 1 ]; then
USE_COLORS="true"
else
USE_COLORS="false"
fi

INPUT_FILE=""
JOURNAL_ARGS=()

while [ $# -gt 0 ]; do
case "$1" in
# Allow using a JSON file as input
--input-file)
INPUT_FILE="$2"
shift 2
;;
*)
JOURNAL_ARGS+=("$1")
shift
;;
esac
done

process_logs() {
if [ -n "$INPUT_FILE" ]; then
cat "$INPUT_FILE"
else
journalctl -u agama-web-server.service -o json "$@"
fi | jq --unbuffered --arg use_colors "$USE_COLORS" -r '
# Helper function to get color based on priority
def get_color:
{
"5": "\u001b[1;91m", # Internal error - Bold Bright Red
"4": "\u001b[1;95m", # Security error - Bold Bright Magenta
"3": "\u001b[1;31m", # Error - Bold Red
"2": "\u001b[93m", # Warning - Bright Yellow
"1": "", # Milestone - Default
"0": "\u001b[90m" # Debug - Grey
}[tostring] // "\u001b[35m"; # Missing or unknown - Magenta

# Helper function to format timestamp (uses UTC)
def format_timestamp:
# The journal time is in microseconds
(tonumber / 1000000 | strflocaltime("%Y-%m-%d %H:%M:%S"));

# Helper function to build the source code location string
def format_location:
[
(.CODE_FILE | select(. != null and . != "") | tostring),
(.CODE_FUNC | select(. != null and . != "") | "(" + tostring + ")"),
(.CODE_LINE | select(. != null and . != "") | ":" + tostring)
]
| join("");

# Helper function to combine hostname and PID
def format_syslog_pid:
[
._HOSTNAME,
(._PID | select(. != null and . != "") | "(" + tostring + ")")
]
| join("")
| select(. != "");

select(.COMPONENT == "libzypp" or .COMPONENT == "zypp-agama-sys") |
# Join the requested fields into a single line
(.ZYPP_LEVEL | get_color) as $color |
[
(.__REALTIME_TIMESTAMP | select(. != null and . != "") | format_timestamp),
(((.ZYPP_LEVEL | select(. != null and . != "") | tostring) // "1") | "<" + tostring + ">"),
format_syslog_pid,
"[" + ((.ZYPP_GROUP | select(. != null and . != "") | tostring) // .COMPONENT) + "]",
format_location,
(.MESSAGE | select(. != null and . != "") | tostring | if $use_colors == "true" and $color != "" then $color + . + "\u001b[0m" else . end)
]
# Remove any missing/empty values and join them with a space
| map(select(. != null and . != ""))
| join(" ")
'
}

if [ "$USE_COLORS" = "true" ] && [ -n "$PAGER" ]; then
process_logs "${JOURNAL_ARGS[@]}" | $PAGER
else
process_logs "${JOURNAL_ARGS[@]}"
fi
Loading
Loading