Skip to content

Commit 0b91522

Browse files
authored
Merge pull request #2863 from input-output-hk/djo/2825/enhance-errors-context
enhance errors context
2 parents ad2ce7b + 750dd12 commit 0b91522

File tree

12 files changed

+137
-44
lines changed

12 files changed

+137
-44
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

DEV-ADR.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,49 @@ To complete
2323
To complete
2424
-->
2525

26+
### 5. Guidelines for writing useful log messages, error context, or error structure
27+
28+
**Date:** 2025-07-25
29+
**Status:** Accepted
30+
31+
### Context
32+
33+
Some errors and logs currently lack enough context to understand the cause of an issue.
34+
35+
This is especially true for failures that occur during requests to or from external sources.
36+
37+
At the same time, adding too much context can make logs noisy and hard to read, and can flood log storage with low-value
38+
or sensitive data.
39+
40+
### Decision
41+
42+
When writing log messages, adding error context, or designing error structures, follow these guidelines:
43+
44+
- **Prefer structured logging for internal context.**
45+
- Add identifiers as structured fields rather than embedding them in the message text (e.g., party id, signed entity
46+
type, beacon, request id, entity id).
47+
- Keep the human-readable message short, put “what happened” in the message and “what it relates to” in structured fields.
48+
49+
- **Avoid unnecessary or sensitive context in logs by default.**
50+
- Do not log secrets or high-risk material (e.g., cryptographic keys, seeds, tokens, credentials).
51+
- Do not log large payloads unless they are required for troubleshooting (see “External sources” below).
52+
53+
- **Handle large debug output explicitly.**
54+
- If a type’s `Debug` output is too large or contains sensitive fields, implement `Debug` manually to provide a safe,
55+
non-exhaustive representation by default.
56+
- Optionally support an “alternate” representation (e.g., `{:#?}`) that includes additional detail when it is safe and useful.
57+
58+
- **External sources: allow exceptions when needed to troubleshoot.**
59+
- For interactions with external sources, it can be acceptable to include additional context such as request/response
60+
payloads **only when necessary** to diagnose issues.
61+
- When logging external payloads, prefer safeguards such as truncation/size limits and logging only at error/debug
62+
level (and redaction when applicable).
63+
64+
### Consequences
65+
66+
- Logs are more readable and actionable.
67+
- Errors are easier to understand and troubleshoot without routinely leaking sensitive data or producing excessive log volume.
68+
2669
### 4. Guidelines for crate test utilities
2770

2871
**Date:** 2025-07-25

mithril-aggregator/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mithril-aggregator"
3-
version = "0.8.0"
3+
version = "0.8.1"
44
description = "A Mithril Aggregator server"
55
authors = { workspace = true }
66
edition = { workspace = true }

mithril-aggregator/src/http_server/routes/signatures_routes.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,14 @@ mod handlers {
5656
.get_signature_registration_total_received_since_startup()
5757
.increment(&[METRICS_HTTP_ORIGIN]);
5858

59+
let payload = message.clone();
5960
let signed_entity_type = message.signed_entity_type.clone();
6061
let signed_message = message.signed_message.clone();
6162

6263
let mut single_signature = match FromRegisterSingleSignatureAdapter::try_adapt(message) {
6364
Ok(signature) => signature,
6465
Err(err) => {
65-
warn!(logger,"register_signatures::payload decoding error"; "error" => ?err);
66+
warn!(logger,"register_signatures::payload decoding error"; "full_payload" => #?payload,"error" => ?err);
6667

6768
return Ok(reply::bad_request(
6869
"Could not decode signature payload".to_string(),
@@ -92,22 +93,22 @@ mod handlers {
9293
{
9394
Err(err) => match err.downcast_ref::<CertifierServiceError>() {
9495
Some(CertifierServiceError::AlreadyCertified(signed_entity_type)) => {
95-
debug!(logger,"register_signatures::open_message_already_certified"; "signed_entity_type" => ?signed_entity_type);
96+
debug!(logger, "register_signatures::open_message_already_certified"; "signed_entity_type" => ?signed_entity_type, "party_id" => &single_signature.party_id);
9697
Ok(reply::gone(
9798
"already_certified".to_string(),
9899
err.to_string(),
99100
))
100101
}
101102
Some(CertifierServiceError::Expired(signed_entity_type)) => {
102-
debug!(logger,"register_signatures::open_message_expired"; "signed_entity_type" => ?signed_entity_type);
103+
debug!(logger, "register_signatures::open_message_expired"; "signed_entity_type" => ?signed_entity_type, "party_id" => &single_signature.party_id);
103104
Ok(reply::gone("expired".to_string(), err.to_string()))
104105
}
105106
Some(CertifierServiceError::NotFound(signed_entity_type)) => {
106-
debug!(logger,"register_signatures::not_found"; "signed_entity_type" => ?signed_entity_type);
107+
debug!(logger, "register_signatures::not_found"; "signed_entity_type" => ?signed_entity_type, "party_id" => &single_signature.party_id);
107108
Ok(reply::empty(StatusCode::NOT_FOUND))
108109
}
109110
Some(_) | None => {
110-
warn!(logger,"register_signatures::error"; "error" => ?err);
111+
warn!(logger,"register_signatures::error"; "full_payload" => #?payload, "error" => ?err);
111112
Ok(reply::server_error(err))
112113
}
113114
},

mithril-aggregator/src/http_server/routes/signer_routes.rs

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,13 @@ mod handlers {
114114
.get_signer_registration_total_received_since_startup()
115115
.increment(&[origin_tag.as_deref().unwrap_or_default()]);
116116

117+
let payload = register_signer_message.clone();
117118
let registration_epoch = register_signer_message.epoch;
118119

119120
let signer = match FromRegisterSignerAdapter::try_adapt(register_signer_message) {
120121
Ok(signer) => signer,
121122
Err(err) => {
122-
warn!(logger,"register_signer::payload decoding error"; "error" => ?err);
123+
warn!(logger,"register_signer::payload decoding error"; "full_payload" => #?payload, "error" => ?err);
123124
return Ok(reply::bad_request(
124125
"Could not decode signer payload".to_string(),
125126
err.to_string(),
@@ -141,32 +142,42 @@ mod handlers {
141142
Ok(reply::empty(StatusCode::CREATED))
142143
}
143144
Err(SignerRegistrationError::ExistingSigner(signer_with_stake)) => {
144-
debug!(logger, "register_signer::already_registered");
145+
debug!(
146+
logger, "register_signer::already_registered";
147+
"registration_epoch" => ?registration_epoch, "party_id" => &signer.party_id,
148+
);
145149

146150
event_transmitter.send(EventMessage::signer_registration(
147151
"HTTP::signer_register",
148152
&signer_with_stake,
149153
signer_node_version,
150-
epoch_str.as_str(),
154+
&epoch_str,
151155
));
152156

153157
Ok(reply::empty(StatusCode::CREATED))
154158
}
155-
Err(SignerRegistrationError::FailedSignerRegistration(err)) => {
156-
warn!(logger,"register_signer::failed_signer_registration"; "error" => ?err);
159+
Err(SignerRegistrationError::InvalidSignerRegistration(
160+
_party_id,
161+
_registration_epoch,
162+
err,
163+
)) => {
164+
warn!(logger,"register_signer::failed_signer_registration"; "full_payload" => #?payload, "error" => ?err);
157165
Ok(reply::bad_request(
158166
"failed_signer_registration".to_string(),
159167
err.to_string(),
160168
))
161169
}
162170
Err(SignerRegistrationError::RegistrationRoundNotYetOpened) => {
163-
warn!(logger, "register_signer::registration_round_not_yed_opened");
171+
warn!(
172+
logger, "register_signer::registration_round_not_yed_opened";
173+
"registration_epoch" => ?registration_epoch, "party_id" => &signer.party_id,
174+
);
164175
Ok(reply::server_error(
165176
SignerRegistrationError::RegistrationRoundNotYetOpened,
166177
))
167178
}
168179
Err(err) => {
169-
warn!(logger,"register_signer::error"; "error" => ?err);
180+
warn!(logger,"register_signer::error"; "full_payload" => #?payload, "error" => ?err);
170181
Ok(reply::server_error(err))
171182
}
172183
}
@@ -395,9 +406,11 @@ mod tests {
395406
async fn test_register_signer_post_ko_400() {
396407
let mut mock_signer_registerer = MockSignerRegisterer::new();
397408
mock_signer_registerer.expect_register_signer().return_once(|_, _| {
398-
Err(SignerRegistrationError::FailedSignerRegistration(anyhow!(
399-
ProtocolRegistrationError::OpCertInvalid
400-
)))
409+
Err(SignerRegistrationError::InvalidSignerRegistration(
410+
"party_2".to_string(),
411+
Epoch(4),
412+
anyhow!(ProtocolRegistrationError::OpCertInvalid),
413+
))
401414
});
402415
let mut dependency_manager = initialize_dependencies!().await;
403416
dependency_manager.signer_registerer = Arc::new(mock_signer_registerer);
@@ -433,7 +446,9 @@ mod tests {
433446
let mut mock_signer_registerer = MockSignerRegisterer::new();
434447
mock_signer_registerer.expect_register_signer().return_once(|_, _| {
435448
Err(SignerRegistrationError::FailedSignerRecorder(
436-
"an error occurred".to_string(),
449+
"party_1".to_string(),
450+
Epoch(4),
451+
anyhow!("an error occurred"),
437452
))
438453
});
439454
let mut dependency_manager = initialize_dependencies!().await;

mithril-aggregator/src/services/certifier/buffered_certifier.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,7 @@ mod tests {
427427
.returning(|_, _| {
428428
Err(CertifierServiceError::InvalidSingleSignature(
429429
OpenMessage::dummy().signed_entity_type,
430+
"party_1".to_string(),
430431
anyhow!("Invalid signature"),
431432
)
432433
.into())

mithril-aggregator/src/services/certifier/certifier_service.rs

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -108,22 +108,27 @@ impl CertifierService for MithrilCertifierService {
108108
) -> StdResult<SignatureRegistrationStatus> {
109109
debug!(
110110
self.logger,
111-
">> register_single_signature(signed_entity_type: {signed_entity_type:?}, single_signatures: {signature:?}"
111+
">> register_single_signature(signed_entity_type: {signed_entity_type:?}, single_signature: {signature:?}"
112112
);
113-
trace!(self.logger, ">> register_single_signature"; "complete_single_signatures" => #?signature);
113+
trace!(self.logger, ">> register_single_signature"; "complete_single_signature" => #?signature);
114114

115115
let open_message = self
116116
.get_open_message_record(signed_entity_type)
117-
.await.with_context(|| format!("CertifierService can not get open message record for signed_entity_type: '{signed_entity_type}'"))?
117+
.await
118+
.with_context(|| format!("CertifierService can not get open message record for signed_entity_type: '{signed_entity_type}'"))?
118119
.ok_or_else(|| {
119-
warn!(self.logger, "register_single_signature: OpenMessage not found for type {signed_entity_type:?}.");
120+
warn!(
121+
self.logger, "register_single_signature: OpenMessage not found for type {signed_entity_type:?}";
122+
"party_id" => &signature.party_id
123+
);
120124
CertifierServiceError::NotFound(signed_entity_type.clone())
121125
})?;
122126

123127
if open_message.is_certified {
124128
warn!(
125129
self.logger,
126-
"register_single_signature: open message {signed_entity_type:?} is already certified, cannot register single signature."
130+
"register_single_signature: open message {signed_entity_type:?} is already certified, cannot register single signature.";
131+
"party_id" => &signature.party_id
127132
);
128133

129134
return Err(CertifierServiceError::AlreadyCertified(signed_entity_type.clone()).into());
@@ -132,7 +137,8 @@ impl CertifierService for MithrilCertifierService {
132137
if open_message.is_expired {
133138
warn!(
134139
self.logger,
135-
"register_single_signature: open message {signed_entity_type:?} has expired, cannot register single signature."
140+
"register_single_signature: open message {signed_entity_type:?} has expired, cannot register single signature.";
141+
"party_id" => &signature.party_id
136142
);
137143

138144
return Err(CertifierServiceError::Expired(signed_entity_type.clone()).into());
@@ -142,13 +148,18 @@ impl CertifierService for MithrilCertifierService {
142148
.verify_single_signature(&open_message.protocol_message.to_message(), signature)
143149
.await
144150
.map_err(|err| {
145-
CertifierServiceError::InvalidSingleSignature(signed_entity_type.clone(), err)
151+
CertifierServiceError::InvalidSingleSignature(
152+
signed_entity_type.clone(),
153+
signature.party_id.clone(),
154+
err,
155+
)
146156
})?;
147157

148158
let single_signature = self
149159
.single_signature_repository
150160
.create_single_signature(signature, &open_message.clone().into())
151-
.await.with_context(|| format!("Certifier can not create the single signature from single_signature: '{signature:?}', open_message: '{open_message:?}'"))?;
161+
.await
162+
.with_context(|| format!("Certifier can not create the single signature from single_signature: '{signature:?}', open_message: '{open_message:?}'"))?;
152163
info!(
153164
self.logger,
154165
"register_single_signature: created pool '{}' single signature for {signed_entity_type:?}.",
@@ -826,7 +837,7 @@ mod tests {
826837
if let Some(err) = error.downcast_ref::<CertifierServiceError>() {
827838
assert!(matches!(
828839
err,
829-
CertifierServiceError::AlreadyCertified(signed_entity) if signed_entity == &signed_entity_type
840+
CertifierServiceError::AlreadyCertified(signed_entity, ..) if signed_entity == &signed_entity_type
830841
),);
831842
} else {
832843
panic!("Unexpected error {error:?}");

mithril-aggregator/src/services/certifier/interface.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use async_trait::async_trait;
22
use thiserror::Error;
33

44
use mithril_common::entities::{
5-
Certificate, Epoch, ProtocolMessage, SignedEntityType, SignedEntityTypeDiscriminants,
5+
Certificate, Epoch, PartyId, ProtocolMessage, SignedEntityType, SignedEntityTypeDiscriminants,
66
SingleSignature,
77
};
88
use mithril_common::{StdError, StdResult};
@@ -27,8 +27,8 @@ pub enum CertifierServiceError {
2727
Expired(SignedEntityType),
2828

2929
/// An invalid signature was provided.
30-
#[error("Invalid single signature for {0:?}.")]
31-
InvalidSingleSignature(SignedEntityType, #[source] StdError),
30+
#[error("Invalid single signature for {0:?} from party '{1}'.")]
31+
InvalidSingleSignature(SignedEntityType, PartyId, #[source] StdError),
3232

3333
/// No parent certificate could be found, this certifier cannot create genesis certificates.
3434
#[error(

mithril-aggregator/src/services/signer_registration/error.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
use thiserror::Error;
22

3-
use mithril_common::{
4-
StdError,
5-
entities::{Epoch, SignerWithStake},
6-
};
3+
use mithril_common::StdError;
4+
use mithril_common::entities::{Epoch, PartyId, SignerWithStake};
75

86
use mithril_cardano_node_chain::chain_observer::ChainObserverError;
97

@@ -42,12 +40,12 @@ pub enum SignerRegistrationError {
4240
EpochService(#[source] StdError),
4341

4442
/// Signer registration failed.
45-
#[error("signer registration failed")]
46-
FailedSignerRegistration(#[source] StdError),
43+
#[error("Signer registration is invalid for party '{0}' and epoch {1}")]
44+
InvalidSignerRegistration(PartyId, Epoch, #[source] StdError),
4745

4846
/// Signer recorder failed.
49-
#[error("signer recorder failed: '{0}'")]
50-
FailedSignerRecorder(String),
47+
#[error("Recording signer registration failed for party '{0}' and epoch {1}")]
48+
FailedSignerRecorder(PartyId, Epoch, #[source] StdError),
5149

5250
/// Signer registration is always closed on a follower aggregator.
5351
#[error("signer registration is always closed on a follower aggregator")]

mithril-aggregator/src/services/signer_registration/follower.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,24 @@ impl MithrilSignerRegistrationFollower {
7070
.signer_registration_verifier
7171
.verify(signer, stake_distribution)
7272
.await
73-
.map_err(SignerRegistrationError::FailedSignerRegistration)?;
73+
.map_err(|err| {
74+
SignerRegistrationError::InvalidSignerRegistration(
75+
signer.party_id.clone(),
76+
epoch,
77+
err,
78+
)
79+
})?;
7480

7581
self.signer_recorder
7682
.record_signer_registration(signer_with_stake.party_id.clone())
7783
.await
78-
.map_err(|e| SignerRegistrationError::FailedSignerRecorder(e.to_string()))?;
84+
.map_err(|err| {
85+
SignerRegistrationError::FailedSignerRecorder(
86+
signer_with_stake.party_id.clone(),
87+
epoch,
88+
err,
89+
)
90+
})?;
7991

8092
self
8193
.verification_key_store

0 commit comments

Comments
 (0)