More Logging

This commit is contained in:
Tim Zöller 2026-01-04 07:28:44 +01:00
parent fa5c04377c
commit 73dc2e1c15

View file

@ -52,20 +52,24 @@ public class WeatherService {
@Transactional
public Optional<WeatherData> fetchWeatherForActivity(Activity activity) {
log.info("=== Weather fetch requested for activity {} ===", activity.getId());
log.info("Weather enabled: {}, API key configured: {}", weatherEnabled, (apiKey != null && !apiKey.isBlank()));
log.info("Weather configuration: enabled={}, API key configured={}, API key length={}",
weatherEnabled, (apiKey != null && !apiKey.isBlank()),
(apiKey != null ? apiKey.length() : 0));
if (!weatherEnabled) {
log.warn("Weather fetching is DISABLED in configuration (fitpub.weather.enabled=false)");
log.warn("Weather fetching is DISABLED in configuration (fitpub.weather.enabled=false). " +
"Set fitpub.weather.enabled=true in application properties to enable.");
return Optional.empty();
}
if (apiKey == null || apiKey.isBlank()) {
log.error("Weather API key is NOT CONFIGURED (fitpub.weather.api-key is empty)");
log.error("Weather API key is NOT CONFIGURED (fitpub.weather.api-key is empty). " +
"Please set fitpub.weather.api-key in application properties.");
return Optional.empty();
}
log.debug("Weather API key present (length: {} chars, starts with: {}...)",
apiKey.length(), apiKey.length() > 4 ? apiKey.substring(0, 4) : "???");
log.info("Weather API key present: length={} chars, first 4 chars={}...",
apiKey.length(), apiKey.length() > 4 ? apiKey.substring(0, 4) : "???");
// Check if weather data already exists
if (weatherDataRepository.existsByActivityId(activity.getId())) {
@ -93,19 +97,18 @@ public class WeatherService {
}
JsonNode firstPoint = trackPoints.get(0);
log.debug("First track point missing lat/lon for activity {}", activity.getId());
log.debug("First track point: {}", firstPoint.toString());
// Check if lat/lon fields exist
if (!firstPoint.has("lat") || !firstPoint.has("lon")) {
log.error("First track point missing lat/lon fields for activity {}",
activity.getId());
log.error("First track point MISSING lat/lon fields for activity {}. Available fields: {}",
activity.getId(), firstPoint.fieldNames());
return Optional.empty();
}
double lat = firstPoint.get("lat").asDouble();
double lon = firstPoint.get("lon").asDouble();
log.info("Extracted location: lat={}, lon={}", lat, lon);
log.info("Extracted location from first track point: lat={}, lon={}", lat, lon);
// Check if activity is recent (within 5 days) - use current weather API
// Otherwise use historical data API (requires paid plan)
@ -117,20 +120,25 @@ public class WeatherService {
WeatherData weatherData;
if (daysDifference <= 5) {
log.info("Activity is recent (within 5 days), fetching current weather");
log.info("Activity is RECENT ({} days old, within 5 day threshold), fetching current weather from OpenWeatherMap", daysDifference);
weatherData = fetchCurrentWeather(lat, lon, activity.getId());
} else {
log.warn("Activity is {} days old (>5 days), historical weather data requires paid API plan. Skipping.", daysDifference);
log.warn("Activity is {} days old (exceeds 5 day threshold). Historical weather data requires OpenWeatherMap paid API plan. Skipping weather fetch.", daysDifference);
return Optional.empty();
}
if (weatherData != null) {
log.info("Successfully fetched and parsed weather data, saving to database");
WeatherData saved = weatherDataRepository.save(weatherData);
log.info("Weather data saved with ID: {}", saved.getId());
return Optional.of(saved);
log.info("Successfully fetched and parsed weather data. Attempting to save to database...");
try {
WeatherData saved = weatherDataRepository.save(weatherData);
log.info("Weather data SUCCESSFULLY SAVED to database with ID: {}", saved.getId());
return Optional.of(saved);
} catch (Exception e) {
log.error("FAILED to save weather data to database: {}", e.getMessage(), e);
return Optional.empty();
}
} else {
log.error("Weather data fetch returned null");
log.error("Weather data fetch returned NULL - check API errors above");
}
} catch (Exception e) {
@ -145,54 +153,93 @@ public class WeatherService {
* Fetch current weather data from OpenWeatherMap.
*/
private WeatherData fetchCurrentWeather(double lat, double lon, UUID activityId) {
log.info("=== fetchCurrentWeather START === activityId={}, lat={}, lon={}", activityId, lat, lon);
try {
String url = String.format("%s?lat=%f&lon=%f&appid=%s&units=metric",
OPENWEATHERMAP_API_URL, lat, lon, apiKey);
String maskedUrl = url.replace(apiKey, "***API_KEY***");
log.info("Making API request to OpenWeatherMap: {}", maskedUrl);
log.debug("API URL: {}", OPENWEATHERMAP_API_URL);
log.debug("Coordinates: lat={}, lon={}", lat, lon);
log.info("Constructed OpenWeatherMap API URL: {}", maskedUrl);
log.info("Request parameters: lat={}, lon={}, units=metric", lat, lon);
long startTime = System.currentTimeMillis();
log.info("Sending HTTP GET request to OpenWeatherMap...");
String response = restTemplate.getForObject(URI.create(url), String.class);
long duration = System.currentTimeMillis() - startTime;
log.info("API request completed in {}ms", duration);
log.info("HTTP request completed in {}ms, response received", duration);
if (response == null) {
log.error("API response is NULL - no data returned from OpenWeatherMap");
log.error("API response is NULL - RestTemplate returned null, no data from OpenWeatherMap");
return null;
}
log.debug("API response length: {} chars", response.length());
log.debug("API response preview: {}", response.length() > 200 ? response.substring(0, 200) + "..." : response);
log.info("API response received: {} characters", response.length());
log.info("API response (first 300 chars): {}",
response.length() > 300 ? response.substring(0, 300) + "..." : response);
log.info("Parsing weather response JSON...");
WeatherData weatherData = parseWeatherResponse(response, activityId);
if (weatherData == null) {
log.error("Failed to parse weather response");
log.error("FAILED to parse weather response - see parsing errors above");
} else {
log.info("Successfully parsed weather data: temp={}°C, condition={}",
weatherData.getTemperatureCelsius(), weatherData.getWeatherCondition());
log.info("Successfully parsed weather data: temp={}°C, feels_like={}°C, condition='{}', description='{}', humidity={}%, pressure={} hPa, wind={} m/s",
weatherData.getTemperatureCelsius(),
weatherData.getFeelsLikeCelsius(),
weatherData.getWeatherCondition(),
weatherData.getWeatherDescription(),
weatherData.getHumidity(),
weatherData.getPressure(),
weatherData.getWindSpeedMps());
}
log.info("=== fetchCurrentWeather END === success={}", (weatherData != null));
return weatherData;
} catch (org.springframework.web.client.HttpClientErrorException e) {
log.error("HTTP CLIENT ERROR from OpenWeatherMap API: Status={}, Body={}",
e.getStatusCode(), e.getResponseBodyAsString(), e);
log.error("=== HTTP CLIENT ERROR (4xx) from OpenWeatherMap API ===");
log.error("Status Code: {}", e.getStatusCode());
log.error("Status Text: {}", e.getStatusText());
log.error("Response Body: {}", e.getResponseBodyAsString());
log.error("Request URL (masked): {}", OPENWEATHERMAP_API_URL + "?lat=" + lat + "&lon=" + lon + "&appid=***&units=metric");
if (e.getStatusCode().value() == 401) {
log.error("AUTHENTICATION FAILED - Check your OpenWeatherMap API key is valid and active");
} else if (e.getStatusCode().value() == 404) {
log.error("API ENDPOINT NOT FOUND - Check coordinates are valid: lat={}, lon={}", lat, lon);
} else if (e.getStatusCode().value() == 429) {
log.error("RATE LIMIT EXCEEDED - Too many API requests. Check your OpenWeatherMap plan limits.");
}
log.error("Exception details:", e);
return null;
} catch (org.springframework.web.client.HttpServerErrorException e) {
log.error("HTTP SERVER ERROR from OpenWeatherMap API: Status={}, Body={}",
e.getStatusCode(), e.getResponseBodyAsString(), e);
log.error("=== HTTP SERVER ERROR (5xx) from OpenWeatherMap API ===");
log.error("Status Code: {}", e.getStatusCode());
log.error("Status Text: {}", e.getStatusText());
log.error("Response Body: {}", e.getResponseBodyAsString());
log.error("OpenWeatherMap service may be experiencing issues. Try again later.");
log.error("Exception details:", e);
return null;
} catch (org.springframework.web.client.ResourceAccessException e) {
log.error("=== NETWORK/CONNECTION ERROR accessing OpenWeatherMap API ===");
log.error("Error message: {}", e.getMessage());
log.error("This could indicate: DNS resolution failure, network connectivity issues, firewall blocking, or SSL certificate problems");
log.error("API URL attempted: {}", OPENWEATHERMAP_API_URL);
log.error("Exception details:", e);
return null;
} catch (org.springframework.web.client.RestClientException e) {
log.error("REST CLIENT EXCEPTION calling OpenWeatherMap API: {}", e.getMessage(), e);
log.error("=== REST CLIENT EXCEPTION calling OpenWeatherMap API ===");
log.error("Exception type: {}", e.getClass().getName());
log.error("Error message: {}", e.getMessage());
log.error("Exception details:", e);
return null;
} catch (Exception e) {
log.error("UNEXPECTED EXCEPTION fetching current weather: {} - {}",
e.getClass().getSimpleName(), e.getMessage(), e);
log.error("=== UNEXPECTED EXCEPTION fetching current weather ===");
log.error("Exception type: {}", e.getClass().getName());
log.error("Error message: {}", e.getMessage());
log.error("Activity ID: {}", activityId);
log.error("Coordinates: lat={}, lon={}", lat, lon);
log.error("Full stack trace:", e);
return null;
}
}
@ -201,8 +248,10 @@ public class WeatherService {
* Parse OpenWeatherMap API response and create WeatherData entity.
*/
private WeatherData parseWeatherResponse(String response, UUID activityId) {
log.debug("=== parseWeatherResponse START === activityId={}", activityId);
try {
JsonNode root = objectMapper.readTree(response);
log.debug("JSON parsed successfully, root node present: {}", root != null);
WeatherData weatherData = new WeatherData();
weatherData.setActivityId(activityId);
@ -210,35 +259,54 @@ public class WeatherService {
// Main temperature data
if (root.has("main")) {
JsonNode main = root.get("main");
log.debug("Parsing 'main' section: {}", main);
weatherData.setTemperatureCelsius(getBigDecimal(main, "temp"));
weatherData.setFeelsLikeCelsius(getBigDecimal(main, "feels_like"));
weatherData.setHumidity(getInteger(main, "humidity"));
weatherData.setPressure(getInteger(main, "pressure"));
log.debug("Extracted main data: temp={}, feels_like={}, humidity={}, pressure={}",
weatherData.getTemperatureCelsius(), weatherData.getFeelsLikeCelsius(),
weatherData.getHumidity(), weatherData.getPressure());
} else {
log.warn("Response JSON does not contain 'main' section");
}
// Wind data
if (root.has("wind")) {
JsonNode wind = root.get("wind");
log.debug("Parsing 'wind' section: {}", wind);
weatherData.setWindSpeedMps(getBigDecimal(wind, "speed"));
weatherData.setWindDirection(getInteger(wind, "deg"));
log.debug("Extracted wind data: speed={} m/s, direction={} degrees",
weatherData.getWindSpeedMps(), weatherData.getWindDirection());
} else {
log.debug("Response JSON does not contain 'wind' section");
}
// Weather condition
if (root.has("weather") && root.get("weather").isArray() && !root.get("weather").isEmpty()) {
JsonNode weather = root.get("weather").get(0);
log.debug("Parsing 'weather' array (first element): {}", weather);
weatherData.setWeatherCondition(getString(weather, "main"));
weatherData.setWeatherDescription(getString(weather, "description"));
weatherData.setWeatherIcon(getString(weather, "icon"));
log.debug("Extracted weather condition: main='{}', description='{}', icon='{}'",
weatherData.getWeatherCondition(), weatherData.getWeatherDescription(),
weatherData.getWeatherIcon());
} else {
log.warn("Response JSON does not contain valid 'weather' array");
}
// Clouds
if (root.has("clouds")) {
weatherData.setCloudiness(getInteger(root.get("clouds"), "all"));
log.debug("Extracted cloudiness: {}%", weatherData.getCloudiness());
}
// Visibility
if (root.has("visibility")) {
weatherData.setVisibilityMeters(root.get("visibility").asInt());
log.debug("Extracted visibility: {} meters", weatherData.getVisibilityMeters());
}
// Rain
@ -246,6 +314,7 @@ public class WeatherService {
JsonNode rain = root.get("rain");
if (rain.has("1h")) {
weatherData.setPrecipitationMm(BigDecimal.valueOf(rain.get("1h").asDouble()));
log.debug("Extracted rain: {} mm/h", weatherData.getPrecipitationMm());
}
}
@ -254,6 +323,7 @@ public class WeatherService {
JsonNode snow = root.get("snow");
if (snow.has("1h")) {
weatherData.setSnowMm(BigDecimal.valueOf(snow.get("1h").asDouble()));
log.debug("Extracted snow: {} mm/h", weatherData.getSnowMm());
}
}
@ -263,20 +333,34 @@ public class WeatherService {
if (sys.has("sunrise")) {
weatherData.setSunrise(LocalDateTime.ofInstant(
Instant.ofEpochSecond(sys.get("sunrise").asLong()), ZoneId.systemDefault()));
log.debug("Extracted sunrise: {}", weatherData.getSunrise());
}
if (sys.has("sunset")) {
weatherData.setSunset(LocalDateTime.ofInstant(
Instant.ofEpochSecond(sys.get("sunset").asLong()), ZoneId.systemDefault()));
log.debug("Extracted sunset: {}", weatherData.getSunset());
}
}
weatherData.setFetchedAt(LocalDateTime.now());
weatherData.setDataSource("openweathermap");
log.info("Successfully parsed complete weather data");
log.debug("=== parseWeatherResponse END === success=true");
return weatherData;
} catch (com.fasterxml.jackson.core.JsonProcessingException e) {
log.error("=== JSON PARSING ERROR ===");
log.error("Failed to parse weather response as JSON");
log.error("Response content: {}", response);
log.error("Parse error: {}", e.getMessage(), e);
return null;
} catch (Exception e) {
log.error("Error parsing weather response: {}", e.getMessage());
log.error("=== UNEXPECTED ERROR parsing weather response ===");
log.error("Exception type: {}", e.getClass().getName());
log.error("Error message: {}", e.getMessage());
log.error("Response content: {}", response);
log.error("Full stack trace:", e);
return null;
}
}
@ -303,23 +387,41 @@ public class WeatherService {
// Helper methods to safely extract values from JSON
private BigDecimal getBigDecimal(JsonNode node, String field) {
if (node.has(field)) {
return BigDecimal.valueOf(node.get(field).asDouble());
if (node != null && node.has(field) && !node.get(field).isNull()) {
try {
return BigDecimal.valueOf(node.get(field).asDouble());
} catch (Exception e) {
log.warn("Failed to extract BigDecimal from field '{}': {}", field, e.getMessage());
return null;
}
}
log.debug("Field '{}' not found or is null in node", field);
return null;
}
private Integer getInteger(JsonNode node, String field) {
if (node.has(field)) {
return node.get(field).asInt();
if (node != null && node.has(field) && !node.get(field).isNull()) {
try {
return node.get(field).asInt();
} catch (Exception e) {
log.warn("Failed to extract Integer from field '{}': {}", field, e.getMessage());
return null;
}
}
log.debug("Field '{}' not found or is null in node", field);
return null;
}
private String getString(JsonNode node, String field) {
if (node.has(field)) {
return node.get(field).asText();
if (node != null && node.has(field) && !node.get(field).isNull()) {
try {
return node.get(field).asText();
} catch (Exception e) {
log.warn("Failed to extract String from field '{}': {}", field, e.getMessage());
return null;
}
}
log.debug("Field '{}' not found or is null in node", field);
return null;
}
}