Can't create a Tracer type
t3hmrman opened this issue · 6 comments
I'm trying to get the WAI
integration working, and I'm almost there but I can't create the last bit I need, a Tracer
value.
I have the functions I want the tracer to run, but due to the type variable on tracer's definition (I think), haskell gets confused about which type to use. Even when I specialize to IO
(instead of using a forall m. MonadIO m
), I get this type error:
src/Tracing/TracingBackend.hs:91:118-124: error: …
• Couldn't match type ‘m’ with ‘IO’
‘m’ is a rigid type variable bound by
a type expected by the context:
forall (m :: * -> *). MonadIO m => SpanOpts -> m Span
at /path/to/src/Tracing/TracingBackend.hs:91:97
Expected type: SpanOpts -> m Span
Actual type: SpanOpts -> IO Span
• In the ‘tracerStart’ field of a record
In the first argument of ‘return’, namely
‘(tracer
{tracerStart = startFn, tracerReport = jaegerAgentReporter j})’
In the expression:
return
(tracer
{tracerStart = startFn, tracerReport = jaegerAgentReporter j})
Here's what the code looks like:
class Tracing T
-- ... other code ...
traceApplication :: t -> Application -> IO Application
instance Tracing TracingBackend where
-- ... other code ...
-- TODO: check to ensure that app wide tracing is enabled
traceApplication t app = if appWideTracingEnabled then instrumentApp app else pure app
where
jaegerOptions = tracingSettings t
appWideTracingEnabled = tracingAppWide $ tracingCfg t
sampling = probSampler $ tracingSamplingRate $ tracingCfg t
propagation = jaegerPropagation
defaultReporter :: FinishedSpan -> IO ()
defaultReporter = makeLoggerReporter t
-- Create Standard ENV for Tracer to use, create standard tracer from the env
mkTracer :: IO (SpanOpts -> IO Span)
mkTracer = newStdEnv sampling >>= pure . stdTracer
-- Update Tracer with a JaegerAgentReporter (instead of log based one), produce the middleware
augmentTracerWithJaeger :: Tracer -> (SpanOpts -> IO Span) -> IO Tracer
augmentTracerWithJaeger tracer startFn = withJaegerAgent jaegerOptions (\j -> return (tracer { tracerStart=startFn
, tracerReport=jaegerAgentReporter j
}))
-- Use the created tracer to make the middleware
makeMiddleware :: Application -> Tracer -> IO Application
makeMiddleware app tracer = pure (opentracing tracer propagation (\activeSpan -> app))
-- Instrument an actual application with the agent reporter
instrumentApp :: Application -> IO Application
instrumentApp app = liftIO (logMsg t INFO "Instrumenting application...")
>> mkTracer
>>= augmentTracerWithJaeger (Tracer undefined undefined)
>>= makeMiddleware app
>>= pure
To me it seems like haskell is expecting the IO to be an m
(as bound by tracer's definition), and cannot get over the requirement.
I've tried setting ScopedTypeVariables
(and abstracting the MonadIO m
type in the typeclass code too), but it still won't convince Haskell that both m
s are the same, I just get the same error with m1
vs m
instead of m
and IO
.
I've looked at Backends.hs
and I'm honestly not sure how it works when you're in the IO monad yet manage to create a Tracer
properly.
Not sure why you need a typeclass here, this seems to complicate things unnecessarily.
The problem at hand is that your startFn
has type SpanOpts -> IO Span
, but Tracer
expects MonadIO m => SpanOpts -> m Span
. liftIO startFn
should make the types align.
Hey @kim thanks for the speedy response. The typeclass has to do with how my app is structured -- I use it to separate the functionality of large-ish components. This typeclass is specific but I think it's helpful when others aren't so specific (and only have one implementation)
I tried to lift the function earlier and it didn't work but I will try again, IIRC the m
is wasn't being deduced as the same m
. I'll give it another shot
Maybe I'm still doing it wrong, here's the code changed:
-- Create Standard ENV for Tracer to use, create standard tracer from the env mkTracer :: MonadIO m => IO (SpanOpts -> m Span)
mkTracer = newStdEnv sampling
>>= \env -> pure (\spanOpts -> liftIO ((stdTracer env) spanOpts))
-- Update Tracer with a JaegerAgentReporter (instead of log based one), produce the middleware
augmentTracerWithJaeger :: MonadIO m => Tracer -> (SpanOpts -> m Span) -> IO Tracer
augmentTracerWithJaeger tracer startFn = withJaegerAgent jaegerOptions (\j -> return (tracer { tracerStart=startFn
, tracerReport=jaegerAgentReporter j
}))
It's a little messy, but I'm super careful to reassemble into a type that matches, but it's unclear (to the compiler) that that m
is the same as the m
in Tracer
. I enabled ScopedTypeVariables
, no difference -- Is it possible this is broken for me since I'm inside a typeclass function?
Here's the error that still emerges:
src/Tracing/TracingBackend.hs:92:118-124: error: …
• Couldn't match type ‘m’ with ‘m1’
‘m’ is a rigid type variable bound by
the type signature for:
augmentTracerWithJaeger :: forall (m :: * -> *).
MonadIO m =>
Tracer -> (SpanOpts -> m Span) -> IO Tracer
at /path/to/src/Tracing/TracingBackend.hs:91:38
‘m1’ is a rigid type variable bound by
a type expected by the context:
forall (m1 :: * -> *). MonadIO m1 => SpanOpts -> m1 Span
at /path/to/src/Tracing/TracingBackend.hs:92:97
Expected type: SpanOpts -> m1 Span
Actual type: SpanOpts -> m Span
• In the ‘tracerStart’ field of a record
In the first argument of ‘return’, namely
‘(tracer
{tracerStart = startFn, tracerReport = jaegerAgentReporter j})’
In the expression:
return
(tracer
{tracerStart = startFn, tracerReport = jaegerAgentReporter j})
• Relevant bindings include
startFn :: SpanOpts -> m Span
(bound at /path/to/src/Tracing/TracingBackend.hs:92:42)
augmentTracerWithJaeger :: Tracer
-> (SpanOpts -> m Span) -> IO Tracer
(bound at /path/to/src/Tracing/TracingBackend.hs:92:11)
src/Tracing/TracingBackend.hs:103:34-41: error: …
• Ambiguous type variable ‘m0’ arising from a use of ‘mkTracer’
prevents the constraint ‘(MonadIO m0)’ from being solved.
Probable fix: use a type annotation to specify what ‘m0’ should be.
These potential instances exist:
instance [safe] MonadIO IO -- Defined in ‘Control.Monad.IO.Class’
...plus 18 instances involving out-of-scope types
(use -fprint-potential-instances to see them all)
• In the second argument of ‘(>>)’, namely ‘mkTracer’
In the first argument of ‘(>>=)’, namely
‘liftIO (logMsg t INFO "Instrumenting application...") >> mkTracer’
In the first argument of ‘(>>=)’, namely
‘liftIO (logMsg t INFO "Instrumenting application...") >> mkTracer
>>= augmentTracerWithJaeger (Tracer undefined undefined)’
Hey @t3hmrman, it's a bit hard to follow because I can't take your code and have a machine typecheck it for me 😬
That said, I guess the signature of augmentTracerWithJaeger
should stay the same as before (with concrete IO
, the MonadIO
constraint is what makes the m
"rigid"), and then use liftIO
in the function body. Apart from that, it doesn't seem like you need to set the tracerStart
field - it can only meaningfully be the stdTracer
currently. Just make a tracer like here and pass that to augmentTracerWithJaeger
, where you only update the tracerReport
field.
Hope this helps.
hey @kim thanks I think that does help -- I'll keep trying at it. The code you linked too should be pretty helpful. I'm basically trying my hardest not to have to set tracerStart
.
Thanks for your help!
After some more fiddling I got it to type check!
traceApplication t app = if appWideTracingEnabled then instrumentApp app else pure app
where
jOpts = tracingSettings t
appWideTracingEnabled = tracingAppWide $ tracingCfg t
sampling = probSampler $ tracingSamplingRate $ tracingCfg t
propagation = jaegerPropagation
defaultReporter :: FinishedSpan -> IO ()
defaultReporter = makeLoggerReporter t
-- Create Standard ENV for Tracer to use, create standard tracer from the env
mkTracer :: MonadIO m => m Tracer
mkTracer = newStdEnv sampling
>>= \env -> liftIO (pure (Tracer (stdTracer env) undefined))
-- Update Tracer with a JaegerAgentReporter (instead of log based one), produce the middleware
augmentTracerWithJaeger :: Tracer -> IO Tracer
augmentTracerWithJaeger tracer = withJaegerAgent jOpts (\j -> return (tracer { tracerReport=jaegerAgentReporter j }))
-- Use the created tracer to make the middleware
makeMiddleware :: Application -> Tracer -> IO Application
makeMiddleware app tracer = pure (opentracing tracer propagation (\activeSpan -> app))
-- Instrument an actual application with the agent reporter
instrumentApp :: Application -> IO Application
instrumentApp app = liftIO (logMsg t INFO "Instrumenting application...")
>> mkTracer
>>= augmentTracerWithJaeger
>>= makeMiddleware app
>>= pure
Hopefully it will help anyone else that finds this issue. I think I did two key thigs that might have helped:
- Removing
ScopedTypeVariables
(I think it was binding them
to my local function, ignoring the constraint inTracer.hs
) - Completely imported
OpenTracing.Standard
(before I was only selectively importing bits of it)