code: def _add_logging(dsk, ignore=None):
      
      ctx = current_action()
      result = {}
  
      # Use topological sort to ensure Eliot actions are in logical order of
      # execution in Dask:
      keys = toposort(dsk)
  
      # Give each key a string name. Some keys are just aliases to other
      # keys, so make sure we have underlying key available. Later on might
      # want to shorten them as well.
      def simplify(k):
          if isinstance(k, str):
              return k
          return "-".join(str(o) for o in k)
  
      key_names = {}
      for key in keys:
          value = dsk[key]
          if not callable(value) and value in keys:
              # It's an alias for another key:
              key_names[key] = key_names[value]
          else:
              key_names[key] = simplify(key)
  
      # 2. Create Eliot child Actions for each key, in topological order:
      key_to_action_id = {
          key: str(ctx.serialize_task_id(), "utf-8")
          for key in keys
      }
  
      # 3. Replace function with wrapper that logs appropriate Action:
      for key in keys:
          func = dsk[key][0]
          args = dsk[key][1:]
          if not callable(func):
              # This key is just an alias for another key, no need to add
              # logging:
              result[key] = dsk[key]
              continue
          wrapped_func = _RunWithEliotContext(
              task_id=key_to_action_id[key],
              func=func,
              key=key_names[key],
              dependencies=[key_names[k] for k in get_dependencies(dsk, key)],
          )
          result[key] = (wrapped_func, ) + tuple(args)
  
      assert result.keys() == dsk.keys()
      return result
docs: Add logging to a Dask graph.
  
      @param dsk: The Dask graph.
  
      @return: New Dask graph.