code: func (containers Containers) Logs(follow bool, timestamps bool, tail string, colorize bool, since string) {
  	var (
  		sources         = make([]multiplexio.Source, 0, 2*len(containers))
  		maxPrefixLength = strconv.Itoa(containers.maxNameLength())
  	)
  	appendSources := func(reader io.Reader, color *ansi.Color, name string, separator string) {
  		if reader != nil {
  			prefix := fmt.Sprintf("%"+maxPrefixLength+"s "+separator+" ", name)
  			sources = append(sources, multiplexio.Source{
  				Reader: reader,
  				Write:  write(prefix, color, timestamps),
  			})
  		}
  	}
  	counter := 0
  	for _, container := range containers {
  		var (
  			logs        = container.Logs(follow, since, tail)
  			stdoutColor *ansi.Color
  			stderrColor *ansi.Color
  		)
  		for _, log := range logs {
  			if colorize {
  				// red has a negative/error connotation, so skip it
  				ansiAttribute := ansi.Attribute(int(ansi.FgGreen) + counter%int(ansi.FgWhite-ansi.FgGreen))
  				stdoutColor = ansi.New(ansiAttribute)
  				// To synchronize their output, we need to multiplex stdout & stderr
  				// onto the same stream. Unfortunately, that means that the user won't
  				// be able to pipe them separately, so we use bold as a distinguishing
  				// characteristic.
  				stderrColor = ansi.New(ansiAttribute).Add(ansi.Bold)
  			}
  			appendSources(log.Stdout, stdoutColor, log.Name, "|")
  			appendSources(log.Stderr, stderrColor, log.Name, "*")
  			counter += 1
  		}
  	}
  	if len(sources) > 0 {
  		aggregatedReader := multiplexio.NewReader(multiplexio.Options{}, sources...)
  		io.Copy(os.Stdout, aggregatedReader)
  	}
  }
docs: // Dump container logs.