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
14 changes: 14 additions & 0 deletions .mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,20 @@
"sentry": {
"type": "http",
"url": "https://mcp.sentry.dev/mcp"
},
"xcodebuildmcp-dev": {
"type": "stdio",
"command": "node",
"args": [
"../../build/cli.js",
"mcp"
],
"env": {
"XCODEBUILDMCP_DEBUG": "true",
"XCODEBUILDMCP_SENTRY_DISABLED": "true",
"XCODEBUILDMCP_IOS_TEMPLATE_PATH": "../../../XcodeBuildMCP-iOS-Template",
"XCODEBUILDMCP_MACOS_TEMPLATE_PATH": "../../../XcodeBuildMCP-macOS-Template"
}
}
}
}
9 changes: 9 additions & 0 deletions example_projects/Weather/Weather/AppLogger.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import OSLog

enum AppLog {
private static let subsystem = "com.sentry.weather.Weather"
static let app = Logger(subsystem: subsystem, category: "app")
static let service = Logger(subsystem: subsystem, category: "service")
static let settings = Logger(subsystem: subsystem, category: "settings")
static let location = Logger(subsystem: subsystem, category: "location")
}
3 changes: 3 additions & 0 deletions example_projects/Weather/Weather/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// Created by Cameron on 30/04/2026.
//

import OSLog
import SwiftUI

enum WeatherSheet: Identifiable {
Expand Down Expand Up @@ -121,10 +122,12 @@ struct ContentView: View {
}

private func selectLocation(_ location: WeatherLocation) {
AppLog.location.notice("select id=\(location.id, privacy: .public) name=\"\(location.name, privacy: .public)\"")
selectedLocation = location
}

private func previewLocation(_ location: WeatherLocation) {
AppLog.location.notice("preview id=\(location.id, privacy: .public) name=\"\(location.name, privacy: .public)\"")
selectedLocation = location
}

Expand Down
46 changes: 40 additions & 6 deletions example_projects/Weather/Weather/Services/WeatherService.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Foundation
import OSLog

struct WeatherService: Sendable {
private let apiClient: any WeatherAPIClient
Expand All @@ -8,23 +9,56 @@ struct WeatherService: Sendable {
}

func defaultLocations() async throws -> [WeatherLocation] {
try await apiClient.defaultLocations().map { dto in
try WeatherLocation(dto: dto)
let start = ContinuousClock.now
AppLog.service.notice("defaultLocations start")
do {
let result = try await apiClient.defaultLocations().map { dto in
try WeatherLocation(dto: dto)
}
AppLog.service.notice("defaultLocations ok count=\(result.count, privacy: .public) elapsed=\(elapsedMs(since: start), privacy: .public)ms")
return result
} catch {
AppLog.service.error("defaultLocations failed error=\(String(describing: error), privacy: .public) elapsed=\(elapsedMs(since: start), privacy: .public)ms")
throw error
}
}

func weather(for locationID: WeatherLocation.ID) async throws -> WeatherReport {
let dto = try await apiClient.weather(for: locationID)
return try WeatherReport(dto: dto)
let start = ContinuousClock.now
AppLog.service.notice("weather start id=\(locationID, privacy: .public)")
do {
let dto = try await apiClient.weather(for: locationID)
let report = try WeatherReport(dto: dto)
AppLog.service.notice("weather ok id=\(locationID, privacy: .public) temp=\(report.current.temperatureC, privacy: .public)C elapsed=\(elapsedMs(since: start), privacy: .public)ms")
return report
} catch {
AppLog.service.error("weather failed id=\(locationID, privacy: .public) error=\(String(describing: error), privacy: .public) elapsed=\(elapsedMs(since: start), privacy: .public)ms")
throw error
}
}

func searchLocations(matching query: String) async throws -> [WeatherLocation] {
try await apiClient.searchLocations(matching: query).map { dto in
try WeatherLocation(dto: dto)
let start = ContinuousClock.now
AppLog.service.notice("search start query=\"\(query, privacy: .public)\"")
do {
let result = try await apiClient.searchLocations(matching: query).map { dto in
try WeatherLocation(dto: dto)
}
AppLog.service.notice("search ok query=\"\(query, privacy: .public)\" count=\(result.count, privacy: .public) elapsed=\(elapsedMs(since: start), privacy: .public)ms")
return result
} catch {
AppLog.service.error("search failed query=\"\(query, privacy: .public)\" error=\(String(describing: error), privacy: .public) elapsed=\(elapsedMs(since: start), privacy: .public)ms")
throw error
}
}
}

private func elapsedMs(since start: ContinuousClock.Instant) -> Int {
let duration = ContinuousClock.now - start
let (seconds, attoseconds) = duration.components
return Int(seconds * 1000) + Int(attoseconds / 1_000_000_000_000_000)
}

extension WeatherService {
static var production: WeatherService {
WeatherService(apiClient: URLSessionWeatherAPIClient(configuration: .production))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import OSLog
import SwiftUI

struct SettingsSheetView: View {
Expand Down Expand Up @@ -53,6 +54,27 @@ struct SettingsSheetView: View {
.scrollIndicators(.hidden)
.background(sheetBackground)
.accessibilityIdentifier("weather.settingsSheet")
.onChange(of: units.temperature) { _, new in
AppLog.settings.notice("temperature=\(new.label, privacy: .public)")
}
.onChange(of: units.wind) { _, new in
AppLog.settings.notice("wind=\(new.label, privacy: .public)")
}
.onChange(of: units.pressure) { _, new in
AppLog.settings.notice("pressure=\(new.label, privacy: .public)")
}
.onChange(of: units.distance) { _, new in
AppLog.settings.notice("distance=\(new.label, privacy: .public)")
}
.onChange(of: units.animationsEnabled) { _, new in
AppLog.settings.notice("animationsEnabled=\(new, privacy: .public)")
}
.onChange(of: units.alertsEnabled) { _, new in
AppLog.settings.notice("alertsEnabled=\(new, privacy: .public)")
}
.onChange(of: units.reduceTransparency) { _, new in
AppLog.settings.notice("reduceTransparency=\(new, privacy: .public)")
}
}

private var sheetBackground: some View {
Expand Down
3 changes: 3 additions & 0 deletions example_projects/Weather/Weather/WeatherApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@
// Created by Cameron on 30/04/2026.
//

import OSLog
import SwiftUI

@main
struct WeatherApp: App {
private let weatherService: WeatherService

init() {
let useMock = ProcessInfo.processInfo.arguments.contains("--mock-weather-api")
weatherService = AppWeatherServiceFactory.makeService()
AppLog.app.notice("launch service=\(useMock ? "mock" : "production", privacy: .public)")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Log message decoupled from actual service selection

Low Severity

The useMock variable independently checks ProcessInfo.processInfo.arguments for the --mock-weather-api flag, but the actual service is created by AppWeatherServiceFactory.makeService() which performs its own independent check. The log message reports based on useMock, not the actual service that was constructed. If the factory's logic ever diverges (e.g., adding environment variable fallback, renaming the flag), the log will silently lie about which service is running — exactly the kind of mismatch this logging PR is trying to help debug.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 8bedaf3. Configure here.

}

var body: some Scene {
Expand Down
Loading