Skip to content

Commit 5c9168d

Browse files
crumplecupclaude
andcommitted
feat(logging): improve calculate_records instrumentation and example guidance
Enhanced debug logging in calculate_records() to meet coding standards and improved example documentation to guide users toward proper setup. Logging Improvements (calculate_records): - Added calc_count to #[instrument] fields for span context - Added structured logging for calc_expression_count and rollback_on_failure - Added debug logs when using optional parameters (session_id, gdb_version) - Added response_length debug log after receiving response - Added detailed error logging with response preview on deserialization failure - Added edit_moment to completion info log Standards Met: ✅ #[instrument] with appropriate skip and fields ✅ Structured context fields (layer_id, calc_count) ✅ Debug events at key points (request, response, optional params) ✅ Error logging before returning (with response preview for debugging) ✅ Info logging on completion with all result fields Example Improvements: - Changed FEATURE_SERVICE_URL from optional to required - Added comprehensive setup instructions for creating own feature service - Explained why public sample servers don't work (reject API key editing) - Added troubleshooting section for common errors (498, 400) - Added warning about data modification before execution - Improved error message formatting with clear action steps User Experience: - Running without FEATURE_SERVICE_URL now shows helpful setup guide - Debug mode (RUST_LOG=debug) provides detailed request/response tracing - Clear explanation of authentication requirements - Step-by-step ArcGIS Online setup instructions Files Modified: - src/services/feature/client/edit.rs - Enhanced calculate_records logging - examples/enterprise/feature_service_field_calculations.rs - Improved docs and validation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 32f9565 commit 5c9168d

2 files changed

Lines changed: 71 additions & 13 deletions

File tree

examples/enterprise/feature_service_field_calculations.rs

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,42 @@
2222
//!
2323
//! # Usage
2424
//!
25-
//! This example requires authentication with a feature service that allows editing.
26-
//! Set the following environment variables in your `.env` file:
25+
//! This example requires authentication with a feature service that you own and control.
26+
//! **Important**: Public sample servers do not accept API key editing operations.
27+
//!
28+
//! ## Setup
29+
//!
30+
//! 1. Create your own hosted feature service in ArcGIS Online:
31+
//! - Log into https://www.arcgis.com
32+
//! - Go to Content → New Item → Feature Layer
33+
//! - Create a simple layer with text fields (e.g., "eventtype", "description")
34+
//! - Enable editing in the settings
35+
//! - Copy the Feature Service URL (ends with /FeatureServer)
36+
//!
37+
//! 2. Set environment variables in your `.env` file:
2738
//!
2839
//! ```env
2940
//! ARCGIS_FEATURES_KEY=your_api_key_with_edit_privileges
30-
//! FEATURE_SERVICE_URL=https://your-server/arcgis/rest/services/YourService/FeatureServer
41+
//! FEATURE_SERVICE_URL=https://services.arcgis.com/YOUR_ORG/arcgis/rest/services/YOUR_SERVICE/FeatureServer
3142
//! LAYER_ID=0
3243
//! ```
3344
//!
34-
//! Run with:
45+
//! 3. Run the example:
46+
//!
3547
//! ```bash
3648
//! cargo run --example feature_service_field_calculations
49+
//!
50+
//! # With debug logging to see request/response details:
51+
//! RUST_LOG=debug cargo run --example feature_service_field_calculations
3752
//! ```
53+
//!
54+
//! ## Troubleshooting
55+
//!
56+
//! - **Error 498 "Invalid Token"**: Your API key doesn't have permissions for this service.
57+
//! Make sure you own the service and the API key has editing privileges.
58+
//!
59+
//! - **Error 400 "Field does not exist"**: The layer schema doesn't match the example.
60+
//! Update the field names in the example to match your layer's fields.
3861
3962
use anyhow::{Context, Result};
4063
use arcgis::{
@@ -68,12 +91,21 @@ async fn main() -> Result<()> {
6891
This key is used for feature editing operations (add/update/delete/calculate).",
6992
)?;
7093

71-
// Get optional service URL and layer ID from environment
72-
let service_url = std::env::var("FEATURE_SERVICE_URL").unwrap_or_else(|_| {
73-
// Default to a public editable test service (if available)
74-
// Note: In production, use a service you control
75-
"https://sampleserver6.arcgisonline.com/arcgis/rest/services/LocalGovernment/Events/FeatureServer".to_string()
76-
});
94+
// Get service URL - REQUIRED for this example
95+
let service_url = std::env::var("FEATURE_SERVICE_URL").context(
96+
"FEATURE_SERVICE_URL not set. This example requires a feature service you own.\n\
97+
\n\
98+
Public sample servers reject API key editing operations.\n\
99+
\n\
100+
To run this example:\n\
101+
1. Create a hosted feature service in ArcGIS Online (https://www.arcgis.com)\n\
102+
2. Enable editing and add some text fields (eventtype, description)\n\
103+
3. Set FEATURE_SERVICE_URL in .env:\n\
104+
\n\
105+
FEATURE_SERVICE_URL=https://services.arcgis.com/YOUR_ORG/arcgis/rest/services/YOUR_SERVICE/FeatureServer\n\
106+
\n\
107+
See example documentation for detailed setup instructions.",
108+
)?;
77109

78110
let layer_id_str = std::env::var("LAYER_ID").unwrap_or_else(|_| "0".to_string());
79111
let layer_id = LayerId::new(layer_id_str.parse::<u32>()?);
@@ -85,6 +117,10 @@ async fn main() -> Result<()> {
85117

86118
tracing::info!("Connected to feature service: {}", service_url);
87119
tracing::info!("Using layer ID: {}", layer_id);
120+
tracing::info!("");
121+
tracing::info!("⚠️ Note: This example will add, modify, and delete features.");
122+
tracing::info!(" Make sure you have a backup if using a production service.");
123+
tracing::info!("");
88124

89125
// ========================================
90126
// Setup: Add test features for calculation

src/services/feature/client/edit.rs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -533,7 +533,7 @@ impl<'a> FeatureServiceClient<'a> {
533533
/// # Ok(())
534534
/// # }
535535
/// ```
536-
#[instrument(skip(self, where_clause, calc_expression, options), fields(layer_id = %layer_id))]
536+
#[instrument(skip(self, where_clause, calc_expression, options), fields(layer_id = %layer_id, calc_count = calc_expression.len()))]
537537
pub async fn calculate_records(
538538
&self,
539539
layer_id: LayerId,
@@ -548,7 +548,13 @@ impl<'a> FeatureServiceClient<'a> {
548548
let where_str = where_clause.into();
549549
let calc_json = serde_json::to_string(&calc_expression)?;
550550

551-
tracing::debug!(url = %url, where_clause = %where_str, "Sending calculate request");
551+
tracing::debug!(
552+
url = %url,
553+
where_clause = %where_str,
554+
calc_expression_count = calc_expression.len(),
555+
rollback_on_failure = ?options.rollback_on_failure,
556+
"Sending calculate request"
557+
);
552558

553559
let mut form = vec![
554560
("where", where_str.as_str()),
@@ -559,9 +565,11 @@ impl<'a> FeatureServiceClient<'a> {
559565
// Add optional parameters
560566
let session_id_str = options.session_id.as_ref().map(|s| s.to_string());
561567
if let Some(ref session_id) = session_id_str {
568+
tracing::debug!(session_id = %session_id, "Using edit session");
562569
form.push(("sessionId", session_id.as_str()));
563570
}
564571
if let Some(ref gdb_version) = options.gdb_version {
572+
tracing::debug!(gdb_version = %gdb_version, "Using geodatabase version");
565573
form.push(("gdbVersion", gdb_version.as_str()));
566574
}
567575
if let Some(rollback) = options.rollback_on_failure {
@@ -592,12 +600,26 @@ impl<'a> FeatureServiceClient<'a> {
592600
}
593601

594602
let response_text = response.text().await?;
603+
tracing::debug!(
604+
response_length = response_text.len(),
605+
"Received calculate response"
606+
);
607+
595608
check_esri_error(&response_text, "calculate")?;
596-
let result: crate::CalculateResult = serde_json::from_str(&response_text)?;
609+
610+
let result: crate::CalculateResult = serde_json::from_str(&response_text).map_err(|e| {
611+
tracing::error!(
612+
error = %e,
613+
response_preview = %&response_text[..response_text.len().min(500)],
614+
"Failed to deserialize CalculateResult"
615+
);
616+
e
617+
})?;
597618

598619
tracing::info!(
599620
success = result.success(),
600621
updated_count = ?result.updated_feature_count(),
622+
edit_moment = ?result.edit_moment(),
601623
"Calculate completed"
602624
);
603625

0 commit comments

Comments
 (0)